Search Unity

Feedback Particle System C# Job System support

Discussion in 'C# Job System' started by richardkettlewell, May 1, 2018.

  1. Webertob

    Webertob

    Joined:
    Dec 12, 2020
    Posts:
    9
    Okay that's unfortunate. Many thanks for your quick response and helping me to not spend to much time on that attempt.

    Just before giving up with the ParticleSystems option. Is their any option to make a custom Emitter or Burst that allows to hand in initial positions and velocity? Or would it be possible to overload the SetParticles function to add new fully defined Particles in addition to the existing ones?

    If not I might look into ECS making a custom solution from scratch.
     
  2. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    Nope, we don't have that.

    Yes. See here https://docs.unity3d.com/ScriptReference/ParticleSystem.SetParticles.html and specifically the overload that takes an offset. Call it like this:

    Code (CSharp):
    1. ps.SetParticles(bornParticlesArray, bornParticlesArrayCount, ps.particleCount);
    Sorry I should have thought of that sooner :)
     
  3. Webertob

    Webertob

    Joined:
    Dec 12, 2020
    Posts:
    9
    That sounds extremely promising. Sorry I haven't found that option myself.

    I have modified my code and it works! Many thanks.

    Just to share for people coming here after me. I ran into a trap using the proposed SetParticles call. Emit also awakes an empty/deactivated system! So if you start with an empty system SetParticles won't activate the system. You need to do that. Otherwise the ParticleSystem is just a visualization of your particles, no internal update! You also need to ensure not to add more particles than the system has capacity.

    Richard again many thanks for your patience and support!
     
    Last edited: Oct 7, 2021
    richardkettlewell likes this.
  4. Webertob

    Webertob

    Joined:
    Dec 12, 2020
    Posts:
    9
    Hi Richard,
    I recognize an unexpected behaviour with ps.SetParticles function. After the significant speedup of SetParticles() vs. Emit(), the focus was on my particle generation code. So I am working with Jobs and Burst compiler. Very promising speed-ups. But I am having trouble when combining ps.SetParticles with NativeArray<ParticleSystem.Particle>.

    To reduce complexity of the situation I took out all the Jobs and Burst, reverting to the old code just using NativeArray<ParticleSystem.Particle> instead of ParticleSystem.Particles[].

    When using a NativeArray<ParticleSystem.Particles> I cannot determine when the PartcielSystem is actually performing a copy / taking the ownership of the Particles presented to it. In the code below I would expect to see black particles. But actually I get red ones rendered on the screen. Is the ParticleSystem doing a copy at all, or does my code keeps ownership for ever?

    Code (CSharp):
    1.             var n = particlesAvailable[0];
    2.             var p = particles[0];
    3.             for (int i = 0; i < n; i++)
    4.             {
    5.                 p = particles[i];
    6.                 p.startColor = Color.black;
    7.                 particles[i] = p;
    8.             }
    9.             ps.SetParticles(particles, n, ps.particleCount);
    10.             for(int i=0; i<n; i++)
    11.             {
    12.                 p = particles[i];
    13.                 p.startColor = Color.red;
    14.                 particles[i] = p;
    15.             }
    Doing the same with ParticleSystem.Particles[] gives Black particles on the screen as expected.
     
  5. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    You always own the NativeArray and the data is copied into the particle system when you call SetParticles. The particle system is never directly using anything in your NativeArray. Hope this helps clarify things - I’m not sure why you’re seeing red particle though..
     
  6. TobiasWeber

    TobiasWeber

    Joined:
    Mar 8, 2021
    Posts:
    7
  7. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    It’s a mistake but we can’t fix it now without breaking existing user code :(

    Someone (me) copied the GetParticles API and forgot to remove “out” from it….
     
  8. XaneFeather

    XaneFeather

    Joined:
    Sep 4, 2013
    Posts:
    97
    Shouldn't the Unity API Updater be able to handle that automatically?
     
  9. TobiasWeber

    TobiasWeber

    Joined:
    Mar 8, 2021
    Posts:
    7
    S*** happens. So let's check what is the implication and way forward.

    Is it just that we have the out Keyword at the wrong place or does the SetParticles function indeed hands back a reference to the internal structure, as it looks like from the black vs. red color expertiment.

    So I could do my code in another fashion. My current approach was to have an own NativeArray of particles, to prepare the particles. Then I hand them over to the PS.

    However I could do it differently. I consider using the ps.GetParticles in combination with an offset that goes beyond the current particles in the system to allocate new particles. Then modify that reference and just forget about it. As my job anyhow modifies data that is already part of the PS. In that case I just need to ensure I am not getting into a race condition with the internal PS update jobs? I would like to run my job, between 2 fixedUpdates, in parallel to other stuff. So being one frame late doesn't matter.

    However if I have to do it within OnParticleSystemUpdate() I guess you ask me to complete the my particle generation job before leaving the function?

    Btw. is there anyway to enforce the PS to update inline with the fixedUpdate cycle? So I want to never drop /interrupt an update cycle. If FPS goes done, this is accepted. But don't skip.
     
  10. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    It's not handing back a reference - the internal structure is a totally different memory layout. It's definitely a copy.
    You don't need to complete your job, unless you need to access the results later in the same frame.
    If you don't complete your job, the native rendering will wait for the job for you, as late as possible before drawing the particles.

    Yes, call ParticleSystem.Simulate in fixed update. This may be bad for performance though - it disables any multithreading of the native update (your job may still run in jobs though, if it is a for-each)
     
  11. TobiasWeber

    TobiasWeber

    Joined:
    Mar 8, 2021
    Posts:
    7
    Hi Richard,
    just to provide short feedback. Thanks to your support I was able to get the ParticleSystem working as intended.

    Emitting many particles at once is most effective using the SetParticles() using an offset beyond the last particle. Yes, the data is copied correctly from my data structures to the PS internal structures, the small glitch on the function arguments (out) does not cause any issue. I had chosen a too short life span for the particles, which gave me the impression I always modify the last set -> my fault. I need to learn more on Debugging with Unity, missing a step-by-step executions with looking into the memory.

    For running physical simulation using the particle system I need to use dedicated calls of the .Simulate() call from within FixedUpdate. Otherwise there is some black magic behind the scene to limit work load. Playing around with time settings not helped.

    Using the job system in combination with the burst compiler to generate the Particles is very efficient.

    Code (CSharp):
    1.     void FixedUpdate()
    2.     {
    3.         fireJobHandle.Complete();
    4.         var n = particlesAvailable[0];
    5.         if (n > 0)
    6.         {
    7.             n = Mathf.Min(n, ps.main.maxParticles - ps.particleCount);
    8.             ps.SetParticles(particles, n, ps.particleCount);
    9.             particlesAvailable[0] = 0;
    10.         }
    11.  
    12.         if (firePulse == true)
    13.         {
    14.             firePulse = false;
    15.             Fire(); //start job to create new particles
    16.         }
    17.         ps.Simulate(Time.fixedDeltaTime, true, false, false);
    18.     }
     
    AndrewRH and richardkettlewell like this.
  12. Ste-RH

    Ste-RH

    Joined:
    May 17, 2019
    Posts:
    145
    Hi all

    Have run into a problem with IJobParticleSystem that I hope is just something silly we are doing in it's usage.

    Using Unity 2020.3.25f1.

    Have a number of duplicate particle systems, all set to max particles 64. The IJobParticleSystem simple adjusts the position of the particle based on it's lifetime percent (essentially along a spline. Everything has been working without issue.

    Adapting what we have, I had a need to increase this and so put it up to 1024. I then ramped up our particle emission such that it was maybe 10 a frame (running at 60Hz). I get a very consistent crash. If I remove the three lines that set the final particle position back at the end of the Execute(ParticleSystemJobData jobData) function, I cannot reproduce the crash at all.

    It crashes with or without using Burst.

    I have tried newer versions of Unity and still see a crash, but not sure it is the same, and maybe less frequent. Certainly linked to particles though still.

    One odd thing I found during my head-banging-debugging is that if I limit back the emitter to not allow (something like) more than half the max particles, I do not see the crash.

    Oh, and another odd thing. I have seen some particles aliveTimePercent value as negative or very large numbers. Not the 0.0 to 100.0 I thought was expected?

    It smacks of threading, but that can't be the case...can it?

    So...what simple thing am I doing wrong?!


    Full on Editor crash logs:

    Code (CSharp):
    1. Received signal SIGSEGV
    2. Stack trace:
    3. 0x00007ff62f517d7c (Unity) tlsf_realloc_align_inplace
    4. 0x00007ff62d62e5bb (Unity) DynamicHeapAllocator::Reallocate
    5. 0x00007ff62d62a459 (Unity) DualThreadAllocator<DynamicHeapAllocator>::Reallocate
    6. 0x00007ff62d626df2 (Unity) MemoryManager::Reallocate
    7. 0x00007ff62d6280a8 (Unity) realloc_internal
    8. 0x00007ff62d0409e4 (Unity) dynamic_array_detail::dynamic_array_data::reallocate
    9. 0x00007ff62daa1b91 (Unity) dynamic_array_detail::dynamic_array_data::reserve
    10. 0x00007ff62d0e2d03 (Unity) ParticleSystemParticles::array_reserve
    11. 0x00007ff62d0cde7e (Unity) ParticleSystem::EmitParticlesExternal
    12. 0x00007ff62d38f2d5 (Unity) ParticleSystem_CUSTOM_Emit_Injected
    13. 0x0000025a24572c97 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.ParticleSystem:Emit_Injected (UnityEngine.ParticleSystem,UnityEngine.ParticleSystem/EmitParams&,int)
    14. 0x0000025a24572ad3 (Mono JIT Code) UnityEngine.ParticleSystem:Emit (UnityEngine.ParticleSystem/EmitParams,int)
    15. 0x0000025a24569c43 (Mono JIT Code) [xxxxxxx.cs:263] xxxxxxx.xxxxxxx:UpdateEmit ()
    16. 0x0000025a24568223 (Mono JIT Code) [xxxxxxx.cs:127] xxxxxxx.xxxxxxx:LateUpdate ()
    17. 0x0000025a28401be8 (Mono JIT Code) (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
    18. 0x00007ffd20d9f1e0 (mono-2.0-bdwgc) [mini-runtime.c:2849] mono_jit_runtime_invoke
    19. 0x00007ffd20d22ac2 (mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
    20. 0x00007ffd20d2bb1f (mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
    21. 0x00007ff62dc2cd34 (Unity) scripting_method_invoke
    22. 0x00007ff62dc28011 (Unity) ScriptingInvocation::Invoke
    23. 0x00007ff62dbf5d24 (Unity) MonoBehaviour::CallMethodIfAvailable
    24. 0x00007ff62dbf5e2c (Unity) MonoBehaviour::CallUpdateMethod
    25. 0x00007ff62d766078 (Unity) BaseBehaviourManager::CommonUpdate<LateBehaviourManager>
    26. 0x00007ff62d76c0da (Unity) LateBehaviourManager::Update
    27. 0x00007ff62d964bbd (Unity) `InitPlayerLoopCallbacks'::`2'::PreLateUpdateScriptRunBehaviourLateUpdateRegistrator::Forward
    28. 0x00007ff62d94c4dc (Unity) ExecutePlayerLoop
    29. 0x00007ff62d94c5b3 (Unity) ExecutePlayerLoop
    30. 0x00007ff62d953059 (Unity) PlayerLoop
    31. 0x00007ff62e60fa91 (Unity) PlayerLoopController::UpdateScene
    32. 0x00007ff62e60dd19 (Unity) Application::TickTimer
    33. 0x00007ff62ea534d1 (Unity) MainMessageLoop
    34. 0x00007ff62ea57521 (Unity) WinMain
    35. 0x00007ff6300e5752 (Unity) __scrt_common_main_seh
    36. 0x00007ffd90507614 (KERNEL32) BaseThreadInitThunk
    37. 0x00007ffd910026a1 (ntdll) RtlUserThreadStart
    EDIT 1:

    Here is the crash callstack I get when I tried Unity 2021 (latest LTS). I also just had it in Unity 2020 using the stripped back attached Execute function.

    Code (CSharp):
    1. 0x00007FF62D62DAA3 (Unity) MemoryProfiler::GetAllocationRoot
    2. 0x00007FF62D996F99 (Unity) MemoryProfiler::UnregisterAllocation
    3. 0x00007FF62D62734B (Unity) MemoryManager::RegisterDeallocation
    4. 0x00007FF62D623C86 (Unity) MemoryManager::Deallocate
    5. 0x00007FF62D627FAA (Unity) free_alloc_internal
    6. 0x00007FF62CF24D11 (Unity) dynamic_array<void * __ptr64 [38],0>::~dynamic_array<void * __ptr64 [38],0>
    7. 0x00007FF6300E525A (Unity) `eh vector destructor iterator'
    8. 0x00007FF62D0C5DD8 (Unity) ParticleSystemParticles::~ParticleSystemParticles
    9. 0x00007FF62D0C7537 (Unity) ParticleSystem::`vector deleting destructor'
    10. 0x00007FF62D64B372 (Unity) delete_object_internal_step2
    11. 0x00007FF62D94A230 (Unity) BatchDeleteStep2Threaded
    12. 0x00007FF62DACF105 (Unity) Thread::RunThreadWrapper
    13. 0x00007FFD90507614 (KERNEL32) BaseThreadInitThunk
    14. 0x00007FFD910026A1 (ntdll) RtlUserThreadStart
    Code (CSharp):
    1.             public void Execute(ParticleSystemJobData particles)
    2.             {
    3.                 var alives = particles.aliveTimePercent;
    4.                 var posX = particles.positions.x;
    5.                 var posY = particles.positions.y;
    6.                 var posZ = particles.positions.z;
    7.  
    8.                 for (int i = 0; i < particles.count; i++)
    9.                 {
    10.                     if (alives[i] < 0.0f || alives[i] > 100.0f)
    11.                     {
    12.                         continue;
    13.                     }
    14.  
    15.                     posX[ i ] = posZ[i];
    16.                     posY[ i ] = 10.0f;
    17.                 }
    18.             }
    EDIT 2: Typically I cannot reproduce this in a simple scene :/

    EDIT 3: Oki, reproduced it in a simple scene. Could it be the 'if' here stopping the job being scheduled? If I comment it out, I don't get the crash...

    Code (CSharp):
    1.     void OnParticleUpdateJobScheduled()
    2.     {
    3.         if( m_ParticleSystem && m_ParticleSystem.particleCount > 0 )
    4.         {
    5.             m_JobHandle = m_ParticleJob.Schedule( m_ParticleSystem );
    6.         }
    7.     }
    EDIT 4: The particle emission is done in LateUpdate(). If I change it to be done in Update() then the crash does not happen. Does this make sense on any level?
     
    Last edited: Feb 2, 2023
  13. Ste-RH

    Ste-RH

    Joined:
    May 17, 2019
    Posts:
    145
    Here is the simple project
     

    Attached Files:

  14. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    You're right, it should be 0-100.

    It seems likely that you've hit an engine bug, though I can't say for sure. Are you able to submit this test project in a bug report so we can track it properly through our tracking software?

    My best guess is that the LateUpdate emission isn't waiting for the burst job to finish, meaning 2 threads are messing with the particle data at the same time. (if so, that's a bug in the engine for us to fix - you could maybe prove the theory by calling m_JobHandle .Complete() in LateUpdate before doing the emission. See what happens)

    From a performance point of view, it would be best if you didn't modify the particle system in the LateUpdate, as it should cause the main thread to wait until the job is finished.
     
    Ste-RH likes this.