3

I am trying to make a flexible particle system for my XNA game, and I've got these interfaces:

public interface IParticle : IUpdateable
{
    bool Alive { get; }
    float Percent { get; }
}

public interface IParticleEffect<T>
    where T : IParticle
{
    void Apply(GameTime time, ref T particle);
}

public interface IParticleEmitter<T> : IUpdateable
    where T : IParticle
{
}

public interface IParticleRenderer<T> : IDrawable
    where T : IParticle
{
}

The idea behind this system is that the client code only needs to derive from IParticle, then make a compatible subclass from IParticleEmitter and IParticleRenderer, and everything else just automagically works behind the scenes. (I'm actually in the middle of writing everything at the moment, but the latter two would have an abstract base class available.)

Anyways, some particle systems like to use mutable structs for optimization purposes, and that's perfectly reasonable. My system only provides the skeleton, and if the client decides that "Hey, structs are the way to go!", then my system should support whatever the client code throws at it. This is why my IParticleEffect.Apply() method takes a particle by ref -- it's cheaper to pass a struct by reference than it is to copy it.

Unfortunately, it breaks when collections are involved, because the foreach iterator doesn't play nicely with objects passed by ref or out. Eric Lippert explains why here.

So, now I have a design decision to make:

  1. Completely disregard structs, and change my constraint to where T: class, IParticle. This potentially hurts future optimizations, but makes it much easier to work with collections.
  2. Change anything that uses ICollection<T> or IEnumerable<T> to IList<T> so I can manually poll it via an indexer. This makes it potentially more powerful, but at the cost of using a deeper interface (list) to store my objects.
  3. Something else

I hope this question isn't too "it depends", but I am curious as to what strategies I can apply here to make it work the way I want.


EDIT: I realized that I could also include a local variable such as:

foreach (var particle in SomeParticleCollection)
{
    var p = particle;
    SomeEffect.Apply(ref p);
}

However, p would still have the net effect of copying it, which is also not ideal.

  • 1
    If performance is a genuine consideration, consider using a for loop instead of a foreach loop. Particularly when iterating over lists, for loops are faster. In 99% of applications this is not a concern, but if performance is a priority then you should probably avoid foreach in your critical path. – Stephen Jun 18 '14 at 6:19
  • @Stephen: Do you have any figures to back that up? – Phoshi Jun 19 '14 at 15:41
  • Googling will also reveal other pages that get the same results. – Stephen Jun 19 '14 at 23:06
3

Premature optimisation is evil.

Drop the structs - go for flexible design. Your initial design seems like you're allowing for flexibility/extensibility. Until you hit performance concerns, stick with the flexible architecture. You'd need to do serious performance profiling to convince everyone that passing structs versus objects is the biggest performance drain in your code - what about the rendering, update logic, AI etc?

Here's an article about whether/how to optimise game programming architectures for performance that I found a good read: http://gameprogrammingpatterns.com/architecture-performance-and-games.html

  • Pretty much what I needed to hear. I'll slap the class constraint on it then, and design around that. Thanks! – Kyle Baran Jun 18 '14 at 6:15
2

Seconding JBRWilkinson in the suggestion to drop the structs.

Beyond what was written in that answer, having a struct implement IUpdateable implies the struct needs must be mutable, which can invite a certain class of bug. Structs, being on the heap, are assigned by copy not by reference, so any change made to a copy will not be reflected back into your database of game objects. I.e. if you write code like

    foreach (var particle in SomeParticleCollection)
    {
        particle.EnabledChanged += myEvent;
    }

it may actually do nothing, because the struct "particle" will be a copy of the struct in the collection. Similarly, anybody implementing an IParticleEffect that changes the incoming IParticle will be disappointed despite it having been passed by reference.

(I suspect that if any system uses mutable structs to implement IUpdateable, they actually keep them boxed most of the time. This would seem defeat the purpose of the optimization, unless I'm missing something.)

For more, see here and, maybe, here

2

Are you sure that you really need such a kind of optimization(ref T)? Have you considered analogous systems or mocked your own? Premature optimization is worse than none at all - you end with decisions that are not obvious at best and incomprehensible at worst.

ref T denotes that the referenced value could be changed by the function. And I don't think that it is a normal behaviour for IParticleEffect<T>. It looks strange.

You are defining your own system and any extraneous particle system will have to be specially plugged into it. At worst case if you find that structure copying greatly reduces the performance you could try to wrap it into some class variable.

public class ParticleStructWrapper<T> : IParticle
 where T: struct, IParticle
{
   T _Particle;
   public ParticleStructWrapper(T particle) {...}

   public Boolean IsAlive { get {return this._Particle.IsAlive;}}
   ...
}

It will still cause a copying for the constructor but if you will use it multiple times the net effect will be small.

But if your original particles must be changed in IParticleEffect then you should either use your original solution (despite that it is not very straightforward) or create more sophisticated proxy.

AFTER

the intent was to have the effect modify the particle

The main idea behind structs is that they allow simple passing by value - for structs with only struct fields such passing makes actual deep copy. With such structs you don't have to fear that someone changes it inside the method, because your original structure is completely unaffected. For small structures copying by value and subsequent use are also faster due to the fact that structures are located on the stack.

You can't change those external particle systems, but you could recommend their developers to read http://msdn.microsoft.com/en-us/library/ms229017(v=vs.110).aspx, https://stackoverflow.com/questions/441309/why-are-mutable-structs-evil.

P.S.: It is quite possible that they have already considered all pros and cons between classes and structs, just their solution isn't the most user friendly, or the particles are designed to be immutable.

  • Actually, the intent was to have the effect modify the particle. It takes any particle as an argument in Apply(), does "something" to it, then life continues on as normal. For example, I have gravity, friction, and an attractor all coded as separate effect classes. – Kyle Baran Jun 18 '14 at 22:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.