Jump to content
A 2021 backup has been restored. Forums are closed and work in progress. Join our Discord server for more updates! ×
SoaH City Message Board

MMF2/CF2.5 Optimization Superthread


Recommended Posts

Yeah, that'd be me, lol. I'm glad the article got you looking into this stuff, cos now I've learned the thing about arrays of fixed fixed values being quicker than looping through spread values, so it's interesting how things go isn't it?

Do you know C++? It sounds like you might do from your posts. If so, remember you can always use rSDK or EDIF to make MMF do the weird and unnatural things you may want it to.

Particularly of interest is EDIF, which as a library contains classes to process object selection for you, so you can play around till your heart's content.

I desperately hope, if I ever get time now I'm a hubby, to make an extension that saves and restores selection states and other little things to make life simpler.

Link to comment
Share on other sites

Yeah, that'd be me, lol. I'm glad the article got you looking into this stuff, cos now I've learned the thing about arrays of fixed fixed values being quicker than looping through spread values, so it's interesting how things go isn't it?

Do you know C++? It sounds like you might do from your posts. If so, remember you can always use rSDK or EDIF to make MMF do the weird and unnatural things you may want it to.

Particularly of interest is EDIF, which as a library contains classes to process object selection for you, so you can play around till your heart's content.

I desperately hope, if I ever get time now I'm a hubby, to make an extension that saves and restores selection states and other little things to make life simpler.

I'm a fledgling software engineer who uses mostly C in work, but yeah, I know C++, Java, Python, and half of everything else that makes the world go round. Anyway, I haven't really experimented a great deal with mingling C and MMF, and while I'm curious, I'm just not sure that's the sort of thing I want to mess with in MMF. Even if I learn how to do this sort of thing really efficiently, at the end of the day I'm more interested in getting things made that anyone just tinkering with MMF can actually use. I should give it a look though just to ease my curiousity and do some more performance tests.

Link to comment
Share on other sites

This gave me an idea to heavily optimize my game... Disable "Ring.Loss", "Moving platforms" and possibly even Ring "Collection" when not needed... How many things should I disable though?

You disable anything that isn't needed, focusing mainly on the big things. Ignore little things that only contain one or two actions, because if you're not careful it can take more energy to open and close the groups than it takes to just process the whole group all the time.

A nifty feature you can add for debugging's sake is this:

Add a counter, and for every event, include an 'add 1 to counter' action. Then at the very top of the events (first line), insert an 'Always - set counter to 0' action.

This will show you how many events you're running per frame. It will let you see any situations where the game is having to work overtime by looking at moments where the counter value jumps very high.

As you switch groups off, you'll be able to see the game running faster and the counter value going down. Once you've got the hang of it, simply delete your counter and all the events and actions associated it will be cleaned up (don't leave the counter in the final game, because the act of counting each event will in itself take up some of your CPU).

Key areas to look for are:

- Objects which have lots of instances (in Sonic games, I guess that would be rings and coins).

- Objects with complicated events (so bad guys with complex AI, etc).

- Objects with lots of fast loops (you don't want to have too many going on at once).

Another important feature if you have games with lots of objects scattered all over the frame is to avoid using ALWAYS.

Try, instead, to use 'Object getting close to window's edge'. This will select objects only within a certain distance of the border of the window. This event is intended to detect objects in the play area that are at risk of leaving it, but if you give a minus value, it'll detect objects outside the play area that are close to entering it (if I remember rightly. It may be the other way around, lol)

Give it a go. :)

EDIT:

Oh, and another tip: Sometimes disabling an object when it goes off screen can look a bit tacky - the player comes back and the object is exactly where it was when he left it. Some ways of getting around this:

a. Don't switch that object off. Switch something else off that won't be noticed but provides the same saving.

b. Only switch off its more time-consuming processes, like Line of Sight tests.

c. Switch it off at a greater distance. A good trick is to switch different features off at different distances - I tend to switch line of sight off the moment it's more than 30px off the screen. But coded animations and movements may be left until 250 to 500px off the screen, depending on the size of your game.

d. Or be sneaky: If your object follows a predictable pattern, keep a record in an alterable value of what the game time was when the object was switched off. When it switches back on, multiply its pattern by the difference in game time that has elapsed since the object was switched off. So if a ball and chain object is swinging in a sinus pattern, and it moves a certain number of degrees per frame, and the game runs at 50fps, then you can work out roughly where it should be when the object is resurrected again.

Just a worked example of the above:

A spiked ball on a chain rotates 5 degrees every frame.

The game runs at 50fps.

The ball is currently at 44 degrees.

When the ball is switched off, the game time is 155,422 milliseconds (just over 00:02:35 in hh:mm:ss)

When the ball switches back on again, the time is 2 minutes later (275,422 ms)

One frame lasts 1/50th of a second, or 250 milliseconds.

So in two minutes (120,000 ms) we should have missed about 120,000/250 frames. (480 frames)

480 frames, at 5 degrees per frame, means the ball would have moved 2,400 degrees if we hadn't switched it off.

So its current angle is 44 degrees + a further 2,400 degrees = 2,444 degrees.

Set the ball's angle to 2,444 degrees and it should look nearly seamless - no one will know that it had just jumped 2 minutes of game time in an instant. It sounds complicated as a worked example, but it's a lot simpler in code.

Link to comment
Share on other sites

Cos you stop applying gravity too :)

But you're quite right, it can lead to bugs with different features being disabled at different times. I tend to group them together, so I'll switch off the whole movement process for each object using the same conditions.

So my psycho ninja badgers will completely stop the entire movement process when they're more than, say, 500px from the window's edge.

EDIT: Assuming of course that you're using custom movements.

Link to comment
Share on other sites

Add a counter, and for every event, include an 'add 1 to counter' action. Then at the very top of the events (first line), insert an 'Always - set counter to 0' action.

That's only going to count events that fire though. While non-triggering events cost less, depending on the terminating condition of an event and whether the event applies to a bunch of objects or a small number at a given time, this may be a really small portion of an event's overall cost.

EDIT: Unless you mean have a separate event each time, but that's going to make reviewing your code nightmarish... I mean, it's not going to hurt performance much to have one value tracking all of this all the time and adding to that value in every event, so you could just leave it in as long as it isn't doubling your actual number of events.

d. Or be sneaky: If your object follows a predictable pattern, keep a record in an alterable value of what the game time was when the object was switched off. When it switches back on, multiply its pattern by the difference in game time that has elapsed since the object was switched off. So if a ball and chain object is swinging in a sinus pattern, and it moves a certain number of degrees per frame, and the game runs at 50fps, then you can work out roughly where it should be when the object is resurrected again.

This is clever, but I can see it resulting in nasty pop-in if you execute it while it's too close to the screen and the enemy is moving too quickly. Of course that's easy enough to get around. Just mentioning another caveat to consider really.

Link to comment
Share on other sites

Oops, yes, I'd forgotten that it won't trigger if the conditions don't all match. That's embarrassing, lol! :P

Like DW pointed out, you want to count even events that don't fire, as their conditions still run.

One tip that I often do if I can't avoid a fast loop and can't reduce the number of objects it handles (like with many on screen particles), a good trick is to split your objects into, say, 4 little groups. So if you have 200 objects, instead of looping all 200 each frame, loop them in groups of 50 each frame. First fifty are processed on frame 1, next fifty on frame 2, and so on. At 50fps, the human eye should barely notice the stagger, but it'll save a lot of horsepower.

Link to comment
Share on other sites

At 60fps I notice that kind of stagger quite a bit even if you are just using two groups... but I'm pretty sensitive to staggered motion. I'm not sure if it will save horse power or not either since it's going to require additional iteration to cull the selection on every event. (every condition of "if altX == 1" requires MMF to loop through all the currently selected objects, which by default is all of them) If you have complex particle systems handling collisions and such it might though.

I tried to make a simple test to gauge the performance, but I couldn't easily get it to not be limited by the object creation event rather than by the actual particle handling.

Link to comment
Share on other sites

Remember, MMF's 'If AltX == 1' even though it iterates lots of objects, it's a lot faster than running a whole event. It's a case of adding a small delay to avoid a big one. You wouldn't get a straight 50% speed boost by simply splitting the loops across 2 frames, I don't think.

I'd likely try and split them into groups once, then use your fixed value method to pick the particles, if I had to fast loop each one. At least that gets a direct reference, I suppose.

I may see if there's some more intuitive way that I could incorporate that into an extension, although I expect ForEach Object makes it a bit redundant.

Might be able to make an extension that at least maintains the array.

Link to comment
Share on other sites

60fps (hertz technically) is the standard refresh rate of NTSC televisions and most monitors. It's also what the original games ran at natively. Except in the PAL regions where they ran at 50fps, which resulted in either slowdown or I think framerate oddities depending on which game it was.

I'd likely try and split them into groups once, then use your fixed value method to pick the particles, if I had to fast loop each one. At least that gets a direct reference, I suppose.

You can't really select groups of objects using fixed values. A fixed value only corresponds to a single object, so you can only pick one object at a time when using select by fixed value.

Link to comment
Share on other sites

Yeah, I meant that I'd split them into groups and store the Fixed Vals of each object in a separate array, one array per group. Then you can loop them separately.

I was planning at some point to make an object that lets you use object selection to prepare an array of fixed values, but I suspect that would end up working almost like for-each object anyway.

Link to comment
Share on other sites

  • 1 month later...
Functions and Loops are just two totally different things that can be used similarly and actually act similarly when you look at the way they are handled in terms of memory and flow control. The primary purpose of a loop is iteration, where you repeat an action many times. A function on the other hand... the primary ideas in that case are reusing code and being able to use the function to get some kind of return value. If you recall from math class, a function has specific input and specific output... though in the case of programming this isn't always true. You can have functions that take nothing and produce nothing, they just aren't especially useful (actually, that's not true, they are, just for the purpose of organization).

In MMF, loops tend to see a lot of use because they work a lot like functions... though more accurately they should be called subroutines since they aren't especially designed to work as functions. You can get around the fact that they don't directly take input and produce output by using a proxy object, which you'll see a lot of that out of me in my MMF projects.

In theory, a loop is basically just a goto statement with termination conditions. It shouldn't be very costly. In practice within MMF, I'm not so sure. The event system makes everything rather complicated in regards to cost, and it's a black box. There is really no knowing what MMF has to do under the hood to enter a loop or to evaluate what events are part of a loop... it could be almost no cost or it could be a totally convoluted system with a large incurred cost. It's difficult to say.

Speaking of this DW,

Is there anything in MMF2 that works like a Go-To event, or a function in which it will stop whatever it's doing and run another group of events before it continues running code down the event list? I guess the way Functions work in C++ (Where if it encounters a Function, it will run that function and get whatever value out of it before it continues the Main loop)? Or is that what MMF2's Loops do?

I THINK i remember someone talking about using Group Activate/Deactivate to make the program break the list, run the specific group you activated, and then continue where it left off. But it doesn't seem to work that way.

Link to comment
Share on other sites

There is no "goto" equivalent, but if what you need is to run a sequence of events from another event on call without doing anything in between, then yes, you can use the "On Loop" event in MMF2 to dictate those sets of events to run and then you can call them from another event simply by using the run loop command with the name of that loop 1 time.

Group Activation/Deactivation only sets whether or not events within a group will be ran during a given frame. You can also use it to change the behavior of a loop by having loop events within groups that are being deactivated and activated.

Link to comment
Share on other sites

  • 2 months later...

I'm not sure if anyone's figured this out yet, and I haven't test it myself, but I think it's worth investigating:

http://www.create-games.com/article.asp?id=1937

Essentially, the article states that if you put all of your fastloop events inside their own groups, then activate them before the loop begins and close them when it's finished, you'll get better performance. When a fastloop is executed, MMF actually checks ALL loop events even if they're a different name, as it needs to do a string comparison before it can determine which events to execute. It won't do this check for events in closed groups, though, which is why this would theoretically boost performance in loop-heavy games.

Of course, this trick will not provide a decent performance boost unless you have a lot of fastloop events. If your slowdown is caused by something else, like large numbers of active objects, then you'd need to try something else.

Link to comment
Share on other sites

I'll create a performance test next weekend to see if this is true. If it is true though, then MMF is completely fucking retarded (because there is really no, and I mean NO reason to handle loops in that manner) and not worth anyone's time from this point forward. I'm advocating Construct and Construct 2 as reasonable alternatives to MMF and I'll be creating sister tests for both.

Also you are wrong Sena, inactive groups aren't completely ignored during runtime, the groups have to evaluate a boolean flag for activity every frame. It's negligible, but it still takes some time. If every event you had were in a group for instance, it could add a small amount of extra burden. But the fact is, if loops have to use string comparison to check for what loop to run against all the looping events, Clickteam fucked up big time. I don't doubt that's how it works either since string operations work on loop names and start loop events, which is freakin' scary. Ideally, loop names would really just be and index against an array storing different lists of events to run

Link to comment
Share on other sites

  • 2 months later...

I'm effectively rezzing an old thread, but there really is no need to make a new thread for this and it's best if the things discussed here are kept in mind by fellow MMF2'ers anyway...

---

So I've been toying with MMF2 again lately for assistance purposes since offering to make a new community engine in a better game-building resource is just out of the question for my intents and purposes. Outside of the ridiculousness of how MMF2 works, it has always been notorious for its poor performance with a lot of basic operations. The greatest threat of all to performance has been discussed here infact; object iteration *shutter*. Although the concept of object iteration is simple once grasped, applying it in MMF2 is no joke, ESPECIALLY if you don't want to kick your high framerates in the nads. The purpose of this post is to shed some of my recent discoveries on my quest of acceptable performance in an age-old program.

First of all, DW's generic pick object discovery is a great one, but in my opinion it is not the best way to go about object iteration. The problem is manual management of an array per object-iteration. This can be a nightmare simply put, as you fumble in the Z dimension so you can have 2-dimensional arrays per group of iterated objects. The generic pick object condition is also just that, generic. For someone new diving into your code, they must trace back to where the fixed value references are pointing to unless the developer remembered to be courteous and lay out some necessary comments. But hey, great performance right? Definitely! But I have found an alternative method that is much more promising...

Tests with the ForEach extension showed that while it is faster to process and easier to use than standard spread value tactics, it is not comparable to the generic pick object method. However, ForEach has an under-the-wraps feature allowing group iteration. Surprisingly, this produces a result with almost the EXACT SAME PERFORMANCE! I theorize this is because when you add objects to a group, it builds a list with their references, and iteration through the group goes through the references within the extension and then selects the desired object with the same method of the generic pick condition. This is wonderful because groups can be named whatever you want, and the ForEach extension contains the meat and potatoes of the operation for you.

To work with this, just add the objects you want to iterate through into a unique group using the ForEach extension (the "Groups" category). Do NOT add based on fixed value as this feature seems to be broken. When all is added and ready to go, iterate through your group using "Start ForEach loop for group...". The ForEach object will iterate through all objects in the group, in the order that they were added in. If you have multiple types of objects, you can use the "on loop for object" condition for each object you want to test, and it'll trigger that event when it comes across the object in the group. If your loop relies on order, just set up the group in the order you want and you've effectively eliminated the need for sub-loops and extra conditions!

Here are the rules to using ForEach properly for a happy day:

  • Update the extension!
  • Do not add or remove objects based on fixed value.
  • The order in which you add objects is important.
  • You MUST remove any object from the group that is destroyed.
  • Duplicate objects can exist in the group, as long as each existance is unique and separated by a "spacer" object (explained below). 1
  • Recursion works given you work around its brokenness (explained below). 2

1. For whatever reason, trying to add the same type of object twice into the same group even if each are unique instances fails, UNLESS if another type of object is added inbetween. This counts for adding based on qualifier too, EVEN IF BOTH OBJECTS ARE DIFFERENT TYPES. To fix this, just add a "spacer" (read useless object) inbetween. This allows the group to be created properly, and since duplicate additions are removed there is close to zilch performance loss from having this object in the group.

2. Recursion in MMF2 is broken in general. Thankfully it works, but loop indexes are not preserved. If you are recursively running loops which iterate multiple times and having them resume is 100% important, you must manually track the indexes within an array. Recursive ForEach loops must be maintained in this same fashion. Also, due to extension implementation, ForEach cannot preserve event order when running a recursive loop. To run a ForEach loop inside of another ForEach loop, break the first loop with a built-in loop, and have that loop run your second ForEach loop. I know, a big pain, but it's the only work-around and the Clickteam community failed to wrap their heads around how this is a problem.

---

I am currently working this system into a family system I made awhile back. When implementation is done, I will release the code here so curious eyes can take a glance. To give a visualization, I can currently have 12 "machines" with 6 parts each running at over 900FPS on a 3GHz machine, while each machine has joint elasticity applied for fictional physics feedback. Yay.

Link to comment
Share on other sites

I just thought I'd verify that yes, fastloop management in MMF2 is horrible and you can see huge performance improvements if you stuff your loops into groups and only activate the group for the specific loop you want to execute. Ridiculous. Something else worth mentioning is that in general, you should name things as minimally as possible, ESPECIALLY fastloops. The longer the string, the slower your game will be. I have found that converting loop names from 12 character to 3 character strings that run on average 150 times per frame increases performance by 20%...

@TailsSena: What is the main issue with your Photo Viewer app? If it is not related to optimization, you could PM me if you'd like to keep this thread clean.

Link to comment
Share on other sites

@Serephim: That was my reaction when I gave a test out of curiosity. >_> Keep in mind though when I say 20%, that's with everything else being light-weight processing in comparison. In the worlds engine, your performance boost wouldn't be as great, but it would most likely be noticeable ESPECIALLY if you bundle loops into groups that you toggle only when needed.

Edit: I finally got the system in a stable state. When doing a direct comparison between my current implementation and the old method that's commonly used, my FPS is soared from 240 to 920, nearly 400% faster than before. I will upload my code sometime tomorrow I suppose; I just need to replace the current graphics with something I can show publicly.

Link to comment
Share on other sites

Double posting due to new content. Please let me know if this isn't approved of here. (I don't like how editing doesn't update the time stamps for the thread.)

---

My second iteration of the family system is done, using the good ole' Doomsday Zone boss to showcase it. It is in stress test mode, so everything will be running as fast as possible. Please read Notes.txt if you have any concerns about how to use and modify this system. MAKE SURE FOREACH IS UPDATED!

http://larkss.thisisourcorner.net/files/FamiliesV2.zip

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...