Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Why does this code works (ParticleSystem.MainModule altering)?

Discussion in 'Scripting' started by MinhocaNice, Feb 12, 2023.

  1. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    Just a heads up, this is not a question asking for help to solve a problem I am having, rather it's a question as to why a certain solution worked when I believed it shouldn't. This is just so people don't prioritize this question over people asking for immediate help, though I do believe this can be important for future readers as it highlights a property that, at least to me, seems quite unintuitive.

    I changed the way I used a particle system in my game so that instead of having to set it as not looping in the editor for each object that used said particle system (since it is used for multiple different game objects and some need it to loop while others do not), I have it set to always looping and instead change it to not loop in script for the objects that don't need it to loop. However, it seems you can't directly modify
    ParticleSystem.main.loop
    since
    ParticleSystem.main
    is not a variable. I tried using
    ParticleSystem.main.loop.Equals(false)
    but that didn't do anything and frankly I never used this property for booleans and have no idea how it works (I assumed it simply set it as equal to the one in parenthesis).

    While searching online I did find that Unity documentation as well as many people instead create a local variable and set it as a specific property of
    ParticleSystem.main
    they want to alter, then change that said variable. For me, this made no sense at all since you are just modifying a new variable and once you make use of
    ParticleSystem.Play()
    it will just use the old settings as you never really modified them, I assumed they were just making examples of how it worked (in the case of Unity docs) or used a second reference to a particle system they are using instead of the prefab reference.

    However, even though logically it made no sense, I decided to just give it a test anyway with the following script (placed inside
    Void Awake ()
    ):

    Code (CSharp):
    1.         if (BloodEruption != null)
    2.         {
    3.             ParticleSystem.MainModule BloodEruptionMainModule = BloodEruption.main;
    4.             BloodEruptionMainModule.loop = false;
    5.         }
    And somehow, it worked flawlessly. Even though later when this particle system is actually played, it never really used the variable
    BloodEruptionMainModule
    and referenced the particle system directly. I was so shocked by this that I decided to come here and just ask why does it work this way? If you can modify a property of a particle system this way, why not just allow devs to modify it directly? How does a variable defined locally and never referenced again can change the way a property works? I am really curious to know why this works this way and whether or not my understanding of coding is flawed from the start.
     
  2. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    717
    This is because MainModule is struct. Structs are value types, not reference types. So, when you get evaluate
    ParticleSystem.main
    , you get a copy of the main module. It has nothing to do with the original data. So, modifying it is kind of like trying to do
    3 = 5;


    Equals
    tests for equality. It doesn't change anything.

    So yeah, this is really weird looking. It doesn't make any sense at first glance: I'd expect to have had to re-assign the modified struct to
    ParticleSystem.main
    to change anything.

    However, if we inspect the definition of
    ParticleSystem.MainModule
    (using a decompiler, since the source isn't provided, as far as I can tell), we see that things aren't so simple...

    Code (CSharp):
    1.         public bool loop
    2.         {
    3.             get
    4.             {
    5.                 return get_loop_Injected(ref this);
    6.             }
    7.             [NativeThrows]
    8.             set
    9.             {
    10.                 set_loop_Injected(ref this, value);
    11.             }
    12.         }
    MainModule.loop
    is a property. So, it has get and set functions. Those functions do...god knows what, since they go into native code! I presume there's some kind of identifier on the struct that's used to tell the native code what needs changing.

    This is definitely an unusual setup, so you were well within you rights to be confused :p
     
    MinhocaNice likes this.
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    I do understand how this seems a bit non-intuitive. Though the approach Unity used here was to avoid garbage generation and still be able to split the internal structures into actual "modules" for organisational purposes.

    As you may know, Unity itself is written in C++. So all core featues are actually implemented in native code. C# and mono / .NET is "only" used as a scripting layer. Most of Unity's C# / .NET classes are actually just wrappers that communicate with the corresponding native C++ class instance. For this every UnityEngine.Object has an internal pointer to the native counterpart.

    The ParticleSystem component is not different in this regard. Though to avoid to have an endless list of properties, they splitted things into modules. As I said to avoid garbage generation, all those modules are represented through a struct on the C# / managed code side. Those structs do not contain any data besides a reference to the corresponding ParticleSystem. Here for example see the MainModule struct. As you may notice, all the "properties" are declared as external properties. So their actual method code maps to a native C++ method. The struct has an internal reference to the ParticleSystem it belongs to. This is used by the native method to actually match the method call to the corresponding ParticleSystem.

    The actual properties of the ParticleSystem like
    main
    just return a struct with the ParticleSystem set, as you can see here.

    So when you have a local variable of one of the module structs, you actually hold a managed reference to the Particle system. The struct type is essentially just a typed wrapper for the ParticleSystem to only map a certain aspect of the ParticleSystem.
     
    MinhocaNice and chemicalcrux like this.
  4. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    Interesting, so if I understood correctly, the
    MainModule.loop
    property uses it's get and set functions to reference it's native code where it somehows instructs the original
    ParticleSystem.main
    struct to have it's values changed based on a newly created local variable? So, whenever changed, it injects the new information from local variables into the original struct, even if that wasn't explicitly demanded in the script.

    This is indeed pretty unintuitive but now it makes a lot more sense, thanks for explaining it.
     
  5. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    Ah, I figured it was just there for comparing, I guess I never really bothered about it since I always just use
    BoolX == BoolY
    for comparing.