Particles... Now in stunning 3D!

Could you elaborate on that a little? I have never really heard of this class before and Iā€™m really confused as on how to use it. I canā€™t seem to insert any of its inherited classes into the editor either.

Oddly enough, part recycling resulted in the CPU cost being practically the same, except with a wider range below and above of about a whole percentage. My minimized particle settings ranged from .7% to 2.5% instead of its previous 1.2% to 2%. The same happened to the regular example I provide in the module (default settings). It used to range from 4.7% to 6%, but now ranges from 4.3% to 7%.

If I make the cache completely full so it is impossible for it to go empty and force another part creation, the variation range drops, but the average CPU cost is actually about .5% higher than before. Iā€™m not sure if Iā€™m just going through too much work to recycle the particles, or if it actually isnā€™t helping, but this is definitely not helping aid the performance.

Currently Iā€™m setting the particles in a folder inside the 3D Emitter ā€œclassā€ (which is literally just another folder), meaning they do store themselves in the Workspace in case the emitter is disabled. But I still want to cache particles if the emitter does happen to be only disabled temporarily.

Most likely, the setting of part properties and the actual rendering dominate the time to update. Even with little or no performance improvement, I would expect pooling and re-use to have some benefits in terms of memory usage, especially on lower-end machines and after the emitter has been going a while, but Iā€™d need to look closely at how instances are allocated and cleaned up to say for sure.

1 Like

Alright cool.
Iā€™d like to know how much localizing functions actually do if ya wanna benchmark it. I canā€™t atm unfortunately.

It saved about .2% to .3% on the two individual particles I use, definitely a noticeable effect. Probably even more effect on large groups of particles. I imagine it helped even more on the lightning generation because that was really heavy on the math.random. Thanks so much for the help!

1 Like

These classes can only be instantiated from code, like:

Instance.new(ā€œSphereHandleAdornā€, game.Workspace)

Their ā€œAdorneeā€ property must be set to a part to be visible.
The CFrame property can be set and will not have the performance issues of setting part CFrames/Mesh offsets.

You can reasonably set several thousand handleadorn cframes every frame.

@Osyris

3 Likes

Please keep in mind PSA: Don't use Instance.new() with parent argument when trying to improve performance :smiley:

This is only especially relevant in regards to network traffic if youā€™re creating them on the server with the intention to replicate them (though you may still want to do this anyway for this usecase since youā€™re likely creating a bunch of particles).

From my experience, the savings from moving from PartInstance -> Adorns (in relation to local performance) dwarf any savings from lua assignment order/micro-optimizations.

1 Like

Woops negate me thenā€¦ Thanks for the heads up!

I donā€™t think thatā€™s true in general - even regardless of the network traffic there is a lot of overhead in parenting first for some cases, specifically for unexpected physics work (in fact physics-related slowdown is why I wrote that post in the first place).

One problem Iā€™ve run into: When making this toggleable, the if statements I added to differentiate between HandleAdornment particles and regular particles actually left the HandleAdornments costing more CPU than the regular parts did (.5% increase). The CPU cost average for regular part particles was doubled to 11-12% instead of ~5-6%.

I donā€™t think this is a good thing. I would love to use handle adornments as a speedy, CPU-conserving alternative, but I donā€™t think that completely switching over to them would be a great idea because that kind of removes the entire purpose of this module in the first place. Thanks for the suggestion though!

EDIT: Wait a minute, just realized I had mesh tween off when dealing with the part-based particles. However, it was still an additional 1-2% for those and, again, the CPU on the HandleAdornments still was higher than before. :confused:

Note sure what youā€™re doing, but these particles sound like they shouldnā€™t be performance-blocked on cpu/lua speed. (I made the switch in a game I am working on recently and had major savings in the ā€œupdatePrepareā€ portion of the rendering update).

Can you show the microprofiler screen of what you see (that gave you the stats you mentioned), and a description/pseudocode of your code?

Iā€™m just using the Script Performance tab in studio. All the code is still in the module, just blocked out. I can try and give you a general idea of what Iā€™m doing but Iā€™ve never really got into optimizing before so Iā€™m not very good at telling whatā€™s good. Iā€™ll see what I can do though next time Iā€™m using Windows.

I originally wrote this part of the module for @Patrickblox in hopes we could use it in Project Autumn and maybe our Star Wars game too, but he said anything above 3% of CPU usage was too expensive and shouldnā€™t be usedā€¦ :frowning:

The better way of measuring if performance increase is using tick() and measuring how long it took for a block of code to execute.

1 Like

could yā€™all ping me when this module gets optimized enough that i can use it without having to worry about lag

thx

So I went through and did a quick average tick benchmark and these were the results.
Average tick was 0.00034065723419189 for default settings
Average tick was 0.00079620313644409 for default with Adornment feature in module but disabled
Average tick was 0.00079818820953369 for default with Adornment feature enabled

Average tick was 0.00018224954605103 for minimized settings
Average tick was 0.00024948358535767 for minimized with Adornment feature in module but disabled
Average tick was 0.00021843004226685 for minimized with Adornment feature enabled

For ā€œAdornment feature in module but disabledā€ I mean that I had all the code in the module to allow for adornments, but disabled it for that particle. Each benchmark consisted of 500 loops in the system, averaging the time each tick took.

My code works out kind of like this:

  • Set up emitter: Creates the emitter, sets everything up, preps settings and compiles arrays. This is done outside of the loop so it does not affect performance.
  • Toggle emitter: This is a second-hand setup that basically decompiles everything and throws the emitter in the loop. This is also initial and should not affect performance, it actually kind of enhances it because it ā€œunpacksā€ everything ahead of time.

  • The Loop:
  • Goes through emitters and emits particles accordingly. When a particle/adornment is created, itā€™ll pull it out of the cache and resets it if already exists, if not, it creates a new one by cloning the base particle or adornment.
    After that, it randomizes the particle stats according to the settings you picked.
    Itā€™ll parent the particle to either the workspace or the base particle, depending on if you used parts or adornments. If adornments, itā€™ll be part of the base particle and will have to have its visibility settings reset.
    Depending on if you had it use meshes or not, itā€™ll use tweenservice to scale them. Transparency also uses tweenservice.
  • Goes through each individual particle and steps itā€™s properties (position with acceleration and gravity into account). If mesh tweening was not used or it is a handle adornment, it manually has to set size here. Problem is if itā€™s a handle adornment, it has 3 different if statements it has to go through due to different types existing with different properties for size.
    CFrame is set here lastly, which is literally a single line of code using the pre-calculated position with an additional CFrame.Angles() for rotations.
  • If the particle happens to despawn, itā€™ll either store it in the cache and reset itā€™s visibility settings (there are more if itā€™s an adornment), or itā€™ll just destroy the particle if the cache is full or not enabled.

For this test I did enable caching and the cache slowly balances itself after some time, but it the average ticks would still stay in around the same proportion otherwise.
Overall, Iā€™d say the adornments would help performance, but if I wanted the full benefit of them, I would have to switch the entire module to using just adornments, not regular parts, and not duel-wielding adornments and parts (which is what I am doing currently). Even if the performance would be significantly improved, it kind of removes the goal I was aiming for, which is using meshparts, material, and lighting on physically 3D particles.

FINAL EDIT (I accidentally posted before I was done writing this, woops): I did leave the adornment code in the module though if you want to take a look. However, I didnā€™t think people were actually going to go through my code and help me optimize it, so I havenā€™t really put any comments in it. Sorry about that.

the goal with switching to handleadorns isnā€™t to save cpu time (but you will if you change from setting part size to adorn height/radius/etc), it saves updateprepare time in the rendering thread

I think this is about as optimized as itā€™s gonna get unless anyone else has any more suggestions. Thanks for all your help guys! There is an example particle I put in the module if you guys want to get a quick demo on how to use it.

Hello @Ashtheking300, you wanted me to notify you when this was done. I donā€™t know how good it is though, lol.

thx fam

I tried it out and it was really cool, I recommend trying it despite what your CPU can handleā€¦ maybe try lowering your graphics?

1 Like