Playmaker Forum

Playmaker Help & Tips => Playmaker Tips & Tricks => Topic started by: Deek on August 27, 2017, 12:10:25 PM

Title: Don't use RunFSMs in instantiated GameObjects!
Post by: Deek on August 27, 2017, 12:10:25 PM
I had a problem in my game for a while in which I got severe lag spikes when instantiating enemies at runtime (see attached screenshot).
Finally took some time to look into it and it turns out it's because those enemies had FSMs on them that are using RunFSM Actions. The templates those RunFSMs use are also big and have even more RunFSMs nested inside of them.

It seems to be that PlayMaker creates a new FSM for every RunFSM the original FSM contains, thus adding up instantiation time in the Awake()-Function (which is actually the best place for heavy operations, but still not efficient enough for big and nested FSMs).

I didn't know that and it's described nowhere that it actually creates new FSMs, I was hoping it would take all actions out of those templates and append those internally to the host FSM or any other way that would be more efficient.

So it should be best to avoid using RunFSMs on objects that have to be instantiated at runtime (and I can't use pooling or create them at scene start since the spawn-amount is indefinite).
Alternative approaches would be to copy over the content of the RunFSMs to the host FSM (useful when the content is small in action/state size) or to outsource the template into a global manager that you ping with a global event instead of creating that template at runtime.
Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: tcmeric on August 27, 2017, 05:56:03 PM
If you do want to use pooling, you can still do so. The asset pooly for example allows you to set a start amount, and a cap amount for the pool. You can also set no cap (or no start amount). It will dynamically increase your pool size as you need it. But, you can still get the benefit of a pool if even if your pool size is unknown to begin.
Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: jeanfabre on August 28, 2017, 03:16:09 AM

 I'll do some research on this to see exactly what's happening.

 but I would not say you should never use it, but in your case indeed, maybe something could be refactored.

 the questions would be what is inside theses fsm you run dynamically, maybe the issue is inside them and not because of the instantiation? have you experiment with this? like do you get still a huge spike if you runfsm on an empty fsm template?

I'll experiment on my side too anyway.


Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: Deek on August 28, 2017, 07:43:21 AM
I always thought pooling could only be used to re-use the same objects over and over again, so having an indefinite amount doesn't it mean that there is still some instantiation happening for every new object?

I tried it with small templates and that doesn't cause any noticeable spikes so it's likely because of the size of the template (see screenshot, the template is also in the attachments but I don't know if those are portable).
I used that template 3 times in the enemies FSM because I wanted to manage all enemy actions globally, causing the big spikes, but that is just the extreme example.

In my custom Inventory every Slot had a fairly complex FSM on them but hadn't have any performance problem at runtime.
So I took some of that logic, put that in a template and ran it with RunFSM (to create a better overview and make it more manageable) and just then it started lagging when opening my Inventory.
It was the same amount of actions and states before but putting them in RunFSM caused my game to lag and pretty much the same goes for the enemies, which means it must be the RunFSM actions that increase the overhead.

I already fixed all those lagspikes by outsourcing the majority to a global manager, but it still means that using RunFSM creates a worse performance than putting everything in one FSM.
As you can see in the profiler screenshot it increases the instantiation time in Awake(), which lead me to believe that it is creating a new FSM for every RunFSM that is prevalent, because I read somewhere in this forum that for every FSM you have in your scene, the loading time increases by like 0.05s.
Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: jeanfabre on August 29, 2017, 12:04:15 AM


You need to refactor this, this is indeed not how you should approach fsms.

 instead of having such a big fsm for so many types of enemies,
have one for each type of enemies:

- easier to debug
- easier to build
- easier to scale up
- no spikes ( not as much should I say)
- no unnecessary loading of 90% of states that will be unused in each case.


Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: djaydino on August 29, 2017, 12:17:03 AM
Another possibility for something like this is using arrays.
That might drop down your state to about 10 states.

But one for each type of enemies might be better.
Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: Deek on August 29, 2017, 06:47:35 AM
Thanks for your input, but this is already the most efficient solution for my kind of requirements.
Since every enemy has a unique set of sounds and loot to drop (all those last branches are unique) I can't group them together by type or any other way (as you can see at the top I already grouped all loot of Humanoids, but that's the most I could combine out of all those enemies).
I know that you mean to split this one FSM into several for each circumstance and that would've decreased the loading times, but not that much that it would've eliminated the problem, especially if I would plan to spawn a lot of enemies at once.

My initial plan was to have all those single "Enemy Actions" inside each enemy, but that is a pain in the ass to debug or maintain and would also create unnecessary actions, that's why this global approach is way better.

Using PlayMaker for years I know that this is not the ideal approach (and not true to the concept it stands behind).
I always try to make my FSMs as small and simple as possible, but this is the only example where that is not feasible.
As I stated earlier, this is only an extreme (and likely bad) example and I already fixed it by having this once in the entire game as a Global Manager instead of bloating every enemy with its content (but because I first thought that RunFSMs wouldn't pose such a performance impact I used them carelessly).

How I approach things shouldn't be of debate here, but rather why RunFSMs seem to cause a bigger loading time than using the same content outside of them (as explained in my Inventory example).

All I wanted for this thread is to make other users aware of potential performance decrease/problems when using RunFSMs and now it's on you to figure out if that's the case and if so, why. ::)
Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: jeanfabre on September 01, 2017, 03:04:10 AM

Yeah, it's always tricky these cases.

This is not the most efficient solution indeed. Your fsm is hosting 90% of content that is not used, that means 90% of data to be processed for nothing. So I would strongly suggest another approach anyway, but that's of course only a suggestion :)

It would improve the situation to refactor because each ennemy only has to load what's truly necessary, so 10 ennemies loading 10 big fsm is always worse than 10 ennemies loading 10 smaller fsm, we agree on that right?

having global/generic approaches always backfire at some point I found. It's indeed less work up front, but ends up cornering you one way or the other, here it's perfs...

Running an Fsm using RunFSM has an impact of performance, so you need to compose around this. For sure the case should be escalated, and I would suggest your file a bug report to us, and if you can share with us this fsm, we can use to benchmark runtime fsm running and improve where possible.

Maybe you could share with me this fsm and I would propose some refactoring? I think it's a good case to work on. I am most confident it's possible to improve the situation with a refactoring. Don't think it's because I try to avoid the fact thatRunFsm has perfs impact, it's just that for you to progress, I think this is the best option, I don't think if runFsm improvements are being made, it would dramatically improve the situation, unfortunatly...

It's known that adding Component at runtime has an impact inside Unity framework, hence why there are so many pooling system, because creating objects at runtime simply takes performances out. So I think the problem here is three fold, Unity, PlayMaker and your fsm.

It's critical in every situation ( development, life etc) to be ready for refactoring :) I was soooo against refactoring when I started dev, each line was so precious to me... I clearly remember how I felt when it seems your loosing precious time for something that should work... But really, in the long run, refactoring and redoing things is really a healthy practice and greatly improve your skills, as well as your project itself. I can ditch 1000's of lines and many fsm's if I think I can do it better. It's not something that should be seen as negative or taken personally. It's really a process we all go through.

So bottom line:

-- I'll forward this to Alex for a review and study how runFsm performances can be improved. Are you part of the beta? if no let me know if you are willing to participate, you could get access to these improvements quicker.

-- Don't hesitate to pm me and send me that fsm, I'll try to see how it can be designed differently to unlock you and give back some perfs to your project.


Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: Broken Stylus on September 19, 2018, 10:14:56 AM

I refrain from using RunFSM for two reasons:

1. Lack of visibility about what's going on inside the FSM.
Thus far the only way I know of to see this ran FSM's activity is to go into the state that holds the action RunFSM and click on the little dinky button that corresponds to this FSM. If there's a better method, it's obviously very well hidden then.
(An independent FSM-Inspector tab would be interesting instead of using Unity's one that has the FSM compete with the other components for screen space, notably.)

2. There's no pool management of instantiated FSMs I know of either.
Creating and destroying FSMs on and on instead of recycling them is not good for performances. I simply have to plan a lot of FSMs in advance by adding them manually onto the objects or subobjects (some of these objects can be templates). I use the code to activate the FSMs in due time. By default they're in disabled states or, if they really need to be called many times, they stay on but doing nothing until they receive proper events.

So, am I forgetting something like super powerful options that could solve some of these issues? :)
Title: Re: Don't use RunFSMs in instantiated GameObjects!
Post by: krmko on September 19, 2018, 10:50:33 AM
Well, Playmaker really should have something like super states where we could nest a complete FSM in the state and have an easy and clear approach to it.

RunFsm is more of a blackbox, and it can have some universal application, but making something with more specific use is not quite worth it sometimes.