We've been working hard on a complete redesign of Construct's functions feature! Previously the Functions plugin was used for making functions in events. As of this week's r143 beta release, Functions are now built-in directly to the event sheet, with many big improvements. This post covers some of the history and summarises the improvements, with some great performance results too!
How functions work
In case you haven't used them before, functions (both old and new) are a way of re-using events. A "Call function" action runs the "On function" event, and then returns to the action and continues from where it left off. For example you could have a function to create several objects and set their initial instance variables. Then instead of having to repeat blocks of actions everywhere you want to do that in your events, you can simply call a function to do that. Functions can also have parameters (values you pass to them) as well as using sub-events and even calling other functions, making them very powerful.
Construct's functions are designed to mimic how functions work in real programming languages. The new built-in functions are actually much closer to functions in programming languages. This is particularly valuable in education, since they're a much better analogy for the programming concept.
Video overview
Last week we published a video demonstrating how the new built-in functions work. Take a look for an overview of how to use them. This blog post has a brief summary of the same, but also covers some additional details like the history of the feature and performance results.
A little history
Construct has come a long way over the years. The old functions feature based on the Function plugin was first introduced in Construct 2 r109 - over 6 years ago in late 2012! This makes it a relatively old feature. It was also developed early in Construct 2's life. It was just over 1 year old at the time - C3 is already older! - and Scirra was a much younger company. Our focus was, wisely for the time, to get features out the door as quickly as possible and move on to the many other essential things that needed doing. Consequently the Function plugin was designed to be implemented quickly, and took many shortcuts. These have now become well-known shortcomings. For example:
- Function names are strings, and don't automatically update when you rename them
- Parameters have no names, types or descriptions, and are only referred to by their zero-based index
- You have to remember how many parameters to add, using the Add parameter option when calling a function
Many users have been asking us to make improvements to this. With the faster and better designed new C3 runtime now released, it was high time to address it for Construct 3. So we did!
The new built-in functions
Here's a summary of how the new built-in functions work.
Adding a function
Functions are now built-in to the event sheet. Instead of adding an event with the Function plugin, it's a new kind of event block - simply choose the new Add function option instead of adding an event.
This appears as a new kind of event block with On function at the top. Note that isn't a condition! While more conditions can be added to this block like a normal event, the On function text is part of the block itself.
Parameters can be added by right-clicking the function and selecting Add parameter.
You can give parameters a name, a description, a specific type (e.g. number or string), and a default value, which is what is pre-filled in to the parameter's value when calling a function.
Parameters appear in the function block. Notice how they look similar to global and local variables. That's because they are a kind of variable too! Function parameters are essentially local variables that are accessible only within the function. As with other variables you can use them by their name in expressions to get their values, and modify them with the system actions to set variables.
Calling a function
Once a function has been added, it appears in the Add action dialog under a new built-in Functions object (which only appears once your project has at least one function in it). This object lists functions in your project as actions. So functions are basically ways of defining custom actions!
Once you choose a function, you can specify parameters (if any) just like any other action. This is one of the places where the biggest usability improvement with the new functions is visible. You get a list of named parameters, with descriptions, and validating the right type is passed, as defined by the function. There's no need to remember how many to add since they're all listed, and any default values are pre-filled.
This function call is then shown much like a normal action, but also note the parameter names are included in the event sheet text, helping ensure it is readable.
When creating a function, you can also give it a return type. If it has a return type, it is used as an expression instead of an action, and you can set the value returned in the expression with the Set return value action. It is then listed as part of the new built-in Functions object, with full support for autocomplete, and also parameter tips. Again this is a huge improvement, since you can see the parameter names and descriptions as you type your expression!
Refactoring functions
Refactoring means updating your logic with new changes - for example renaming a function. The new built-in functions have much better support for refactoring making it far easier to make changes to your project - especially ones with far-reaching effects. It now covers:
- Find all references can be used for functions, as well as any of its parameters
- Renaming a function automatically updates everywhere it is used - both as actions and expressions
- Renaming a parameter automatically updates everywhere it is used - just like a local variable - including its description everywhere the function is called
- Adding a parameter automatically adds a new parameter with its default value everywhere the function is called - including both actions and expressions
- Deleting a parameter removes all references to it, in both actions and expressions - and of course can be undone to bring everything back
- Moving parameters to rearrange their order automatically updates all references to the function, correspondingly rearranging the parameters the function is called with - even in expressions
In short you should be able to make changes to your functions with the confidence your entire project updates automatically, with no extra work necessary!
Improved performance
The new functions feature is directly built-in to the engine, which makes it a lot easier to optimise. Previously one of the shortcuts used by the old Function plugin was that calling a function re-used the "trigger" mechanism in the event engine - the same process as runs triggers like 'On start of layout'. However this was not built for the purpose of calling functions and added some overhead. Now calling a built-in function uses a purpose-built code path that directly jumps in to the event to be run, cutting out a lot of overhead. The fact it's built in to the engine allows several other optimisations which we've applied. As a result, the new built-in functions are far faster! Here are performance results of some function benchmarks we've used before: firstly making 5 million calls, simply to measure the overhead of a function call, and then a test calling functions as expressions recursively to naively calculate the 30th Fibonacci number. The measurements are the time to execute in milliseconds, so lower is better.
These results show calling 5 million functions is over 3.5x faster than the old functions, and calculating the Fibonacci number is also 2x faster. So using built-in functions can bring a major performance improvement!
It's also worth highlighting how much the C3 runtime has improved since it was first released. The original C3 runtime introduced in r95 was fundamentally more efficient than the C2 runtime - but then we added compiling expressions to JavaScript in r101.2 for even faster performance, and then added a binding optimisation in r102 to boost it even further. Here are the results for the 5m function calls performance test starting with the C2 runtime, all the way through these optimisations, and up to the new built-in functions.
As you can see things have steadily improved over time, with two big jumps when first moving to the C3 runtime, and now when moving to built-in functions. This highlights that going from the C2 runtime all the way to C3's new built-in functions speeds up performance to over 10 times faster! This is an incredible result and is a sign of all the hard work we've put in optimising the engine - covering an entire rewrite of the engine; performance tuning along the way; and then redesigning core features to be fundamentally more efficient.
Conclusion
Construct 3's new built-in functions are much easier to use, and significantly faster. They should especially make a huge improvement to managing large projects with thousands of events - both in ease of use and performance. They're still a great tool to use in any size project, and they should be a much more useful teaching tool in education, since they more closely resemble functions in programming languages.
The new built-in functions effectively allow you to create custom actions and custom expressions on the new built-in functions object. There is scope to extend this to more custom event features in future - such as adding custom actions to existing objects, or even building entire plugins out of events. These are compelling ideas and something we're thinking about for the long-term - but that would be a massive project and as ever we're a small team with a huge amount to do in all areas of the product and business, so we probably won't be able to act on that soon. Still it's a fascinating direction to be moving in and we're looking forwards to seeing what you can build!
You can try out the new built-in functions feature right now, using Construct 3 r143 or newer!