Difference between revisions of "Wml optimisation"

From The Battle for Wesnoth Wiki
(Further reading: - added a link to advanced optimalisations and hacks)
(Further reading)
Line 184: Line 184:
 
==Further reading==
 
==Further reading==
  
[http://wiki.wesnoth.org/Advanced_Optimisations_and_Hacks More complex optimisations] - read this only if you believe you know everything!
+
*[http://wiki.wesnoth.org/Advanced_Optimisations_and_Hacks More complex optimisations] - read this only if you believe you know everything!
 
*[http://www.wesnoth.org/macro-reference.xhtml Macro reference]
 
*[http://www.wesnoth.org/macro-reference.xhtml Macro reference]
 
*[[LuaWML]]
 
*[[LuaWML]]
 
*[[ConventionsWML]]
 
*[[ConventionsWML]]

Revision as of 19:47, 29 October 2013

Work in progress...

The basics

What you should know first

How do I optimise my code?

So, you've written your campaign, scenario or era and it works. Congratulations, but your work is far from over! If your campaign or scenario is big enough, you'll often notice that it runs very slowly, and you need to do something about it. Or maybe you have a powerful computer and you'd like to make sure that other, weaker machines won't have troubles with running your content. Or perhaps, you just want to learn how to write code that not only works, but is also efficient.

The first thing to know, since apparently it's not stressed enough, macros do not improve performance! Macros only make your code more readable and easier to maintain. Try to refrain from using big macros, and more importantly, avoid using macros inside macros (inside macros, inside macros... yes, I've seen it happen, and yes I've tried doing it myself when I was new to wml). Under the covers, WML macros are replaced by the code they represent, and if there are macros inside macros (especially big ones), lines of code multiply horribly fast.

What's a better way to write code that appears often? Custom events' and while loops! By defining a custom event, and firing it when needed, you'll save a lot of code lines, make your code nicer to read and much more efficient. Same goes for the while loop: instead of copy/pasting your code, try to use a loop, whenever you can. Let's take a look at this example problem: whenever side 1 unit enters a village, you want to place 5 walking corpses around this village. Let's say there are 6 villages in question with coordinates (1,1), (2,2), (3,3), (4,4), (5,5), (6,6).

Inefficient example

   #WARNING: this code was written on the run, and may not be 100% accurate
   #NOTE: remember, this should work alright, but it is not efficient! Imagine if you have much more villages in mind, or you want to spawn more zombies
   
   #define SPAWN_ZOMBIES X Y SIDE
   [unit]
   side={SIDE}
   type=Walking Corpse
   x,y={X},{Y}
   placement=map_passable
   [unit]
   [unit]
   side={SIDE}
   type=Walking Corpse
   x,y={X},{Y}
   placement=map_passable
   [unit]
   [unit]
   side={SIDE}
   type=Walking Corpse
   x,y={X},{Y}
   placement=map_passable
   [unit]
   [unit]
   side={SIDE}
   type=Walking Corpse
   x,y={X},{Y}
   placement=map_passable
   [unit]
   [unit]
   side={SIDE}
   type=Walking Corpse
   x,y={X},{Y}
   placement=map_passable
   [unit]
   #enddef
   
   [event]
   name=moveto
       [filter]
       side=1
       x,y=1,1
       [or]
       x,y=2,2
       [/or]
       [or]
       x,y=3,3
       [/or]
       [or]
       x,y=4,4
       [/or]
       [or]
       x,y=5,5
       [/or]
       [or]
       x,y=6,6
       [/or]
       [/filter]
   
       {SPAWN_ZOMBIES $unit.x $unit.y 2}
   [/event]

Looks pretty innocent, right? But imagine that there are more villages. Or better yet, imagine you want to increase the amount of zombies, you'll have to copy/paste even more, and it's easy to make a mistake while doing it.

More efficient example

   #first, let's define our custom event:
   [event]
   name=spawn_zombies
   first_time_only=no
   #it's easy to forget, but you have to set first_time_only in custom events too
   {VARIABLE i 0}
   [while]
       [variable]
       name=i
       less_than_equal_to=5
   #repeating the loop 5 times for 5 zombies, note how easy it is now to change the amount of zombies you want to spawn!
       [/variable]
       [do]
           [unit]
           side=2
           type=Walking Corpse
           x,y=$unit.x,$unit.y
   #unit variable is passed to this event from the parent event. 
           placement=map_passable
           [unit]
           {VARIABLE_OP i add 1}
       [/do]
   [/while]
   {CLEAR_VARIABLE i}
   [/event]
   
   [event]
   name=moveto
       [filter]
       side=1
       x,y=1,1
       [or]
       x,y=2,2
       [/or]
       [or]
       x,y=3,3
       [/or]
       [or]
       x,y=4,4
       [/or]
       [or]
       x,y=5,5
       [/or]
       [or]
       x,y=6,6
       [/or]
       [/filter]
   
       [fire_event]
       name=spawn_zombies
       [/fire_event]
   [/event]

Fired events vs macros

But if fire events are so cool and efficient, what is the advantage of using macros? Good question. As you can see, custom events have one issue: it's harder to set dynamic variables in fired events. When defining macros, you specify arguments and those arguments are declared whenever we're using that macro and can be changed in any way you, and more importantly, someone else wants. Remember, that you might want to start a bigger project and invite someone to help you with the code. Macros are obvious and transparent in usage - when reading a macro definition you always know what is the changing variable and what is a constant. In custom events, it is not so obvious.

Let's take a look at one of the default macros included with wesnoth:

   #define ABILITY_REGENERATES
   # Canned definition of the Regenerate ability to be included in an [abilities]
   # clause.
   [regenerate]
       value=8
       id=regenerates
       name= _ "regenerates"
       female_name= _ "female^regenerates"
       description= _ "Regenerates:
   The unit will heal itself 8 HP per turn. If it is poisoned, it will remove the poison instead of healing."
       affect_self=yes
       poison=cured
   [/regenerate]
   #enddef

This is a good macro, because replacing it by a custom event would be complicated and messy and it wouldn't save a lot of code, anyway. Another example is

   #define GENERIC_UNIT SIDE TYPE X Y
   # Creates a generic unit of TYPE belonging to SIDE at X,Y, which has a
   # random name, gender and traits (just like a recruited unit).
   [unit]
       side={SIDE}
       type={TYPE}
       x={X}
       y={Y}
       generate_name=yes
       random_traits=yes
       random_gender=yes
       upkeep=full
   [/unit]
   #enddef

This time, making a custom event would be fairly easy, but it would be much less transparent in usage. The code {GENERIC_UNIT 1 Spearman 12 3} is self-explanatory, simple and versatile.


Analogies to "real" programming

Now that you know how to use macros and custom events to optimise your code, let's take a moment to think how you could use your knowledge outside Wesnoth. You've probably heard about c++, python, lua or any other programming language. Just like them, WML has some common features but under slightly different names. For example, instead of making custom events, you're making custom functions and procedures that can be used further in your code in a very similar way. That's why understanding optimisation is so important - it'll help you understand real programming problems, and develop good habits!

Further reading