2

The code below is working, "Particle" is an instance of class "ParticleSystem".

"Particle.emission" is a get-only property return struct "ParticleSystem.EmissionModule"

"em.rate" is a property, the type is struct "ParticleSystem.MinMaxCurve"

ParticleSystem.EmissionModule em = Particle.emission; 
em.rate = new ParticleSystem.MinMaxCurve(5);

My problem is, why the code above can change the rate in "Particle" instance?

Note the struct is not reference, so it cannot be changed directly, or it will cause CS1612

Currently, my guess is the struct "ParticleSystem.EmissionModule" stored some references that can link or relate to the original "Particle" instance?

Developer
  • 133
  • 2
  • 10
  • It's very hard to tell without a complete example - are you *certain* that `EmissionModule` is a struct? And that if you use `Particle.emission.rate` you'll see the new rate? – Jon Skeet Oct 19 '16 at 11:44
  • Yes, I am certain, so that why I can provide their type and describe this problem. – Developer Oct 19 '16 at 11:57
  • How have you ascertained that changing `rate` actually changes the emission rate for `Particle`? What happens if you log the values of `Particle.emission.rate` before and after the change you make? – Jon Skeet Oct 19 '16 at 12:08
  • Changed, originally is 10, after is 5 – Developer Oct 19 '16 at 12:11
  • Are you able to see that just by logging `Particle.emission.rate` before and after though? It's possible that `EmissionModule` has a reference back to the `Particle` it comes from, but that would certainly seem odd... – Jon Skeet Oct 19 '16 at 12:28
  • @JonSkeet I dug into this and found out that there is a `ParticleSystem` reference saved into a temporary variable in the `EmissionModule` . That variable is then controlled by the rate `rate` property variable. Feel free to modify my answer if anything looks wrong. – Programmer Oct 19 '16 at 13:25
  • 1
    How truly horrible. Violates all expectations... – Jon Skeet Oct 19 '16 at 15:32

1 Answers1

7

I noticed this behavior too but I found out what's happening after digging deeper with .NET Reflector.

The complete example of your code with the latest Unity version:

ParticleSystem particle = GetComponent<ParticleSystem>();
ParticleSystem.EmissionModule em = particle.emission;
em.rate = new ParticleSystem.MinMaxCurve(5);

Things to bear in mind:

ParticleSystem is a class.

EmissionModule is a struct.

To change the Particle's emission rate in Unity 5 and above, you must get the ParticleSystem.emission then store it in a temporary EmissionModule(struct) then you can modify it's rate variable

How does this work?

When you do:

ParticleSystem particle = GetComponent<ParticleSystem>(); 

or create/instantiate new ParticleSystem or attach one through the Editor, Unity will create new EmissionModule instance. EmissionModule has a an internal constructor that takes ParticleSystem as a parameter. Unity will immediately pass the current ParticleSystem instance to this EmissionModule constructor and that instance is stored in a temporary variable in the EmissionModule struct for later use.

It looks something like this:

private ParticleSystem tempParticleSystem;
internal EmissionModule(ParticleSystem particleInstance)
{
    this.tempParticleSystem = particleInstance;
}

When you do:

ParticleSystem.EmissionModule em = particle.emission;

Unity will create new instance of EmissionModule from the current particle (particle) and return it. That will contain that ParticleSystem (tempParticleSystem) reference that was saved. Remember that ParticleSystem is a class, so the reference is still there. The emission property only have the get accessor. No set accessor. Because of this, it is a read only property.

The emission property looks something like this:

public EmissionModule emission
{
    get
    {
        return new EmissionModule(this);
    }
}

Finally, when the you do:

em.rate = ....

or change the emission rate, that saved reference is used to change the Particle rate in the native side of Unity which is written in C++.

public ParticleSystem.MinMaxCurve rate
{
    get
    {
        ParticleSystem.MinMaxCurve curve = new ParticleSystem.MinMaxCurve();
        getParticleRate(this.tempParticleSystem, ref curve);
        return curve;
    }
    set
    {
        setParticleRate(this.tempParticleSystem, ref value);
    }
}

To simplify this, we can call this a result of a class (ParticleSystem) inside a struct (EmissionModule).

Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Thanks, this also how I guessed before, struct "EmissionModule" store the reference of particle class! – Developer Oct 19 '16 at 13:53
  • Yup that was the first thing I thought too. Wanted to verify before posting and it turns out to be true after using Reflector to check it. – Programmer Oct 19 '16 at 13:58