- Joined
- Apr 16, 2007
- Messages
- 177
Hi, me again
As I am again and again seeing people that don't want to ( for any matters, like they simply cannot, or they are to lazy, or they aren't good at writing effective code ) write JASS . Instead they use the point and click method of the Graphical User Interface ( short GUI )... which brings lot's of problems with it. The both most common ones are memory leaks - and SI ( single instanceablility ) code.
There are lots of tutorials about how to handle memory leaks... But for multi instanceability ( short: MUI ) there is only one - even if there are some other ways.
One of it: The Ringbuffer.
//note that even the ringbuffer needs JASS. But I will explain all you need, and
// that will mostly be "set", used to declare a variable...
//Note that this is meant to be a very beginner friendly tutorial. To make ppl that allready know a lot of this stuff still not getting bored,
//I colored all the things beginners mght not know in green, so you can easily skip the green parts. They are not part of the ringbuffer itself.
// Still, things that are only interesting for advanced ppl won't be featured here - perhaps l8r. So plz don't cry "But there are easier ways use this 5345245 line long JASS code instead!"
1.The Ringbuffer
1.1 How to make one?
1.2 Write
1.3 Read
1.4 Limits...
1.5 Why use this pretty difficult stuff, and not Tom_Jones?
1. The Ringbuffer
What is a ringbuffer?
First, we should make a simple buffer:
Imagine a checkered piece of paper. You have a pencil and a rubber. Now, you go from one side of the paper, going from casket to casket, and write something in it. If you want to read or change something, you have to go back to the field where you have written it for that you will need for directions: one casket up, one down, one to the right or one to the left... If you want to Save something new, you have to look on the whole paper, until you found an empty space... And If there isn't any... Tough luck!
Now, you want to make it more efficient:
You simply stick two parallel sides of paper together - and got a ring of paper.
Delete all but one lines, and you no longer need for directions... Simply one - the left!
because when you walk to the left for a longer time, you will end at the point where you have started!
That is a ringbuffer.
1.1 How to make one?
As first, you need the memory on which you can save your things. As it should be something like a line of paper, going from one point to another point without gaps you don't have the right to access to, I would use an array.
You can imagine (JASS) arrays as a bunch of variables that are numerated. A single variable in an array is called "field", and is accessed by the array in which it is and its number.
So MyArray[0] would access to the first field of the array MyArray. Do you wonder why I started with 0, and not with 1? In computer sciences, many things are started with 0, as 0 has many advatages towards 1. One of it is the following:
As in computers you cannot "stick" two sides "together", you have to tell the computer that one to the left of the last field, he shall restart at the first field - and you do that by using modulo.
Modulo (short mod, in JASS it is ModuloInteger) is the function that calculates the remainder of a given divisor and divident.
so a mod b = c, with a as divisor, b as dividend and c as remainder.
Examples:
17 mod 5 = 2
9 mod 11 = 9
-4 mod 2 = 0
-5 mod 2 = -1
You see, if there is no remainder, mod will return 0... which is the first field of our array, then.
So to go one field left, you write:
[your field + 1] mod [the number of fields + 1] = your field
What do these ones do?
the first one goes to the next field, the second one has to be there as your last field isn't your first one. Your first field is the field one to the left of the last field
Try it out: we are currently on field 4 of 5.
so
4 + 1 mod 5 +1 = 5 mod 6 = 5.
If we wouldn't add that one:
4 + 1 mod 5 = 5 mod 5 = 0
Instead of going to field five, we would go to field 0 - we would skip the last field.
1.2 Write
Now you can easily write into it, using a "for Integer A " loop:
You check if the current field is empty, (If MyArray[ integer A ] == no ... ) then.. If it is empty, you simply write into it - and save "integer A" in a local variable. Else we simply get to the next field
Oh wait! What is such a local variable?
It is a variablke that is initialized ( =created ) and destroyed within the same function ( ="trigger" ). You have to initialize them using custom script by typing "local [your type] [your name]". As name you should use something representative and descriptive, like "counter", "triggeringunit", "pickedhero" , "loosingplayer", and as type you have to use what it saves: "integer" , "real" , "string" and so on. Locals have to be initialized at the beginning of your trigger.
For this purpose we need "integer".
So:
Now we set the variable to the current field number using the "set" command.
Sets the content of [variable] to the input. In the input you can use anything you want of the same type as the variable, so integers for integer variables, reals for real variables, and so on. Even the variable itself can be used here;It will have the value it had before setting it. So
will increase the variable by two. It is equivalent to
, but for matters of maintainability I will allways use
Note that for accessing to global variables ( those created in trigger editor ) , you will need the prefix "udg_" ( which means "user defined global").
So we will use these actions(in this example, we will save a unit):
Note that you have to end the loop when you saved it once, else you will make really heavy bugs... You do that by using the "exitwhen [boolean] " command. It exits the running loop, when the boolean == true. So "exitwhen bj_forLoopAIndex== 5" would exit if integer A ( in JASS bj_forLoopAIndex ) equals to 5. If it does not, the loop will precede untill it reaches the next exitwhen, and check again there.
Now we saved our unit into the buffer.
1.3 Read
To refer to one saved unit, we simply take a global unit variable (called "tmpunit), and set it in custom script to the saved unit:
And then you can use it for any purposes. Note that if you use waits between the loading of the unit and any actions using it, you have to reload the unit again using this line before you use it! And at the end of each trigger where you used it and no longer need it, you have to remove the unit again from the buff:
custom script: set udg_UnitBuffer[ringbufpointer] = null
1.4 The limits....
Well this is really difficult. As more used fields makes the code less fast, it should be a small field number... But then again, the less fields you use, the fewer things you can save in it...
You might need some time to get the best balancing. For the beginning I simply would use 45 fields. There are only few games that need more. Most games even need only 16 or less. Still, being carefully is allways better than being risky...
1.5 Why use this pretty difficult stuff, and not Tom_Jones? Or gamecaches?
1. Gamecaches are pretty slow
2. Using Tom_Jones MUI can cause problems with same-name vars.
3. I can Use it even as Globals, like if you wanna create a unit per player which can be replaced.... And then you have to change the field used for the former unit. Still, you would have to exchange the pointer between triggers using globals, mostly arrays (perhaps even a "cheated" multidimensional array for multiple units per player). This is espacially usefull if you do not know how many units you have and who owns these units. Still they are important for gameplay.
4. It's just nice to have one
5. There are trigger complexes that need Ringbuffers simply to transfer data with a global ringbufpointer. PE to transfer units between triggers, then the one trigger writes and then the other trigger reads.
Greetings Serra
//The references on Tom_Jones are from The Helper, where I posted this tutorial.. Look here http://www.thehelper.net/forums/showthread.php?p=491528#post491528
As I am again and again seeing people that don't want to ( for any matters, like they simply cannot, or they are to lazy, or they aren't good at writing effective code ) write JASS . Instead they use the point and click method of the Graphical User Interface ( short GUI )... which brings lot's of problems with it. The both most common ones are memory leaks - and SI ( single instanceablility ) code.
There are lots of tutorials about how to handle memory leaks... But for multi instanceability ( short: MUI ) there is only one - even if there are some other ways.
One of it: The Ringbuffer.
//note that even the ringbuffer needs JASS. But I will explain all you need, and
// that will mostly be "set", used to declare a variable...
//Note that this is meant to be a very beginner friendly tutorial. To make ppl that allready know a lot of this stuff still not getting bored,
//I colored all the things beginners mght not know in green, so you can easily skip the green parts. They are not part of the ringbuffer itself.
// Still, things that are only interesting for advanced ppl won't be featured here - perhaps l8r. So plz don't cry "But there are easier ways use this 5345245 line long JASS code instead!"
1.The Ringbuffer
1.1 How to make one?
1.2 Write
1.3 Read
1.4 Limits...
1.5 Why use this pretty difficult stuff, and not Tom_Jones?
1. The Ringbuffer
What is a ringbuffer?
First, we should make a simple buffer:
Imagine a checkered piece of paper. You have a pencil and a rubber. Now, you go from one side of the paper, going from casket to casket, and write something in it. If you want to read or change something, you have to go back to the field where you have written it for that you will need for directions: one casket up, one down, one to the right or one to the left... If you want to Save something new, you have to look on the whole paper, until you found an empty space... And If there isn't any... Tough luck!
Now, you want to make it more efficient:
You simply stick two parallel sides of paper together - and got a ring of paper.
Delete all but one lines, and you no longer need for directions... Simply one - the left!
because when you walk to the left for a longer time, you will end at the point where you have started!
That is a ringbuffer.
1.1 How to make one?
As first, you need the memory on which you can save your things. As it should be something like a line of paper, going from one point to another point without gaps you don't have the right to access to, I would use an array.
You can imagine (JASS) arrays as a bunch of variables that are numerated. A single variable in an array is called "field", and is accessed by the array in which it is and its number.
So MyArray[0] would access to the first field of the array MyArray. Do you wonder why I started with 0, and not with 1? In computer sciences, many things are started with 0, as 0 has many advatages towards 1. One of it is the following:
As in computers you cannot "stick" two sides "together", you have to tell the computer that one to the left of the last field, he shall restart at the first field - and you do that by using modulo.
Modulo (short mod, in JASS it is ModuloInteger) is the function that calculates the remainder of a given divisor and divident.
so a mod b = c, with a as divisor, b as dividend and c as remainder.
Examples:
17 mod 5 = 2
9 mod 11 = 9
-4 mod 2 = 0
-5 mod 2 = -1
You see, if there is no remainder, mod will return 0... which is the first field of our array, then.
So to go one field left, you write:
[your field + 1] mod [the number of fields + 1] = your field
What do these ones do?
the first one goes to the next field, the second one has to be there as your last field isn't your first one. Your first field is the field one to the left of the last field
Try it out: we are currently on field 4 of 5.
so
4 + 1 mod 5 +1 = 5 mod 6 = 5.
If we wouldn't add that one:
4 + 1 mod 5 = 5 mod 5 = 0
Instead of going to field five, we would go to field 0 - we would skip the last field.
1.2 Write
Now you can easily write into it, using a "for Integer A " loop:
You check if the current field is empty, (If MyArray[ integer A ] == no ... ) then.. If it is empty, you simply write into it - and save "integer A" in a local variable. Else we simply get to the next field
Oh wait! What is such a local variable?
It is a variablke that is initialized ( =created ) and destroyed within the same function ( ="trigger" ). You have to initialize them using custom script by typing "local [your type] [your name]". As name you should use something representative and descriptive, like "counter", "triggeringunit", "pickedhero" , "loosingplayer", and as type you have to use what it saves: "integer" , "real" , "string" and so on. Locals have to be initialized at the beginning of your trigger.
For this purpose we need "integer".
So:
Code:
custom script : local integer ringbufpointer
Code:
set [variable] = [input]
Code:
set ringbufpointer = ringbufpointer + 2
Code:
set ringbufpointer = 2 + ringbufpointer
Code:
set var = var [operator] [operand]
Code:
set udg_myglobal = ringbufpointer
So we will use these actions(in this example, we will save a unit):
Note that you have to end the loop when you saved it once, else you will make really heavy bugs... You do that by using the "exitwhen [boolean] " command. It exits the running loop, when the boolean == true. So "exitwhen bj_forLoopAIndex== 5" would exit if integer A ( in JASS bj_forLoopAIndex ) equals to 5. If it does not, the loop will precede untill it reaches the next exitwhen, and check again there.
Code:
For each integer between 0 and [the number of fields you use]
If UnitBuffer[ integer A] equals to (==) no unit , then
set UnitBuffer[ integer A ] = ( the unit you got )
custom script: set ringbufpointer = bj_forLoopAIndex
custom script: exitwhen true
else
Now we saved our unit into the buffer.
1.3 Read
To refer to one saved unit, we simply take a global unit variable (called "tmpunit), and set it in custom script to the saved unit:
Code:
set udg_tmpunit = udg_UnitBuffer[ ringbufpointer ]
custom script: set udg_UnitBuffer[ringbufpointer] = null
1.4 The limits....
Well this is really difficult. As more used fields makes the code less fast, it should be a small field number... But then again, the less fields you use, the fewer things you can save in it...
You might need some time to get the best balancing. For the beginning I simply would use 45 fields. There are only few games that need more. Most games even need only 16 or less. Still, being carefully is allways better than being risky...
1.5 Why use this pretty difficult stuff, and not Tom_Jones? Or gamecaches?
1. Gamecaches are pretty slow
2. Using Tom_Jones MUI can cause problems with same-name vars.
3. I can Use it even as Globals, like if you wanna create a unit per player which can be replaced.... And then you have to change the field used for the former unit. Still, you would have to exchange the pointer between triggers using globals, mostly arrays (perhaps even a "cheated" multidimensional array for multiple units per player). This is espacially usefull if you do not know how many units you have and who owns these units. Still they are important for gameplay.
4. It's just nice to have one
5. There are trigger complexes that need Ringbuffers simply to transfer data with a global ringbufpointer. PE to transfer units between triggers, then the one trigger writes and then the other trigger reads.
Greetings Serra
//The references on Tom_Jones are from The Helper, where I posted this tutorial.. Look here http://www.thehelper.net/forums/showthread.php?p=491528#post491528