- Joined
- Dec 14, 2005
- Messages
- 10,532
Efficient allocation and deallocation of array indexes – Multiinstanceabilty for Timers, Custom Values, and more
Difficulty (Relative to GUI)7/10 (Moderate)
Within [trigger] tags, <> surrounding text denotes that that text is a field which you should fill yourself. The text enclosed is a description of what you should put there.
Demo maps for all content explained in this tutorial are available at the bottom of the post.
Often, when you are trying to achieve one of many things such as a multiinstanceable spell which uses a timer, or an extended custom value system, you run into problems with how to approach it. How would you achieve such a feat? The logical approach would be to use a series of arrays.
However, there are two significant problems posed by this; how do you make sure a slot is not permanently occupied, and how do you make sure you don't have a massive performance hit from attempting to fix this taking hostage of slots?
The answer is surprisingly simple. Let's follow along the idea of an extended custom value.
Variables
To begin, we'll need some variables.
The first step is to decide what our custom value will need to be able to store. In this case, let's say we want to be able to store a Real and a Unit.
Our variables thus are:
System variables
Name | Type | Array? | Value |
CurrentIndex | Integer | No | 0 (Default) |
CVMaxIndex | Integer | No | 0 (Default) |
CVFreeIndex | Integer | No | 0 (Default) |
CVAvailableIndexes | Integer | Yes | N/A |
Custom value arrays
Name | Type | Array? | Value |
CVUnitDataArray | Unit | Yes | N/A |
CVRealDataArray | Real | Yes | N/A |
Note: No variables are reusable between systems, except CurrentIndex.
Code
Allocating an array slot
This should simply give the unit the last available freed custom value, or if none exists, extend the list.
Note: YourUnit is the unit to get/lose a Custom Value extension.
-
Allocate
-
Actions
- Set CurrentIndex = CVFreeIndex
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- CurrentIndex Equal to 0
-
Then - Actions
- Set CVMaxIndex = (CVMaxIndex + 1)
- Set CurrentIndex = CVMaxIndex
-
Else - Index
- Set CVFreeIndex = CVAvailableIndexes[CurrentIndex]
-
If - Conditions
- Set CVAvailableIndexes[CurrentIndex] = -1
- Unit - Set Custom Value of YourUnit to CurrentIndex
- Set CVUnitDataArray[CurrentIndex] = <Initial value of the unit's Unit custom value>
- Set CVRealDataArray[CurrentIndex] = <Initial value of the unit's Real custom value>
-
Actions
Basically, the system gets the current available index. If that index is 0 (nonexistant), then it increases the indexes available by 1, and gives it that index.
Otherwise, it moves the next current available index to the one freed before the last (now occupied) index is freed, stored via an array.
Freeing an array slot
Freeing an index is much simpler; the last available freed index is stored in an array at the position of the newly freed index, and the newly freed index is set to be the currently available index.
-
Deallocate
-
Actions
- Set CurrentIndex = (Custom Value of YourUnit)
- Set CVAvailableIndexes[CurrentIndex] = CVFreeIndex
- Set CVFreeIndex = CurrentIndex
- -------- The next bit is optional, if you want to be paranoid about cleaning up for yourself, which is generally a good idea --------
- Unit - Set Custom Value of YourUnit to 0
-
Actions
Allocate...
CurrentIndex = 0
CVMaxIndex = 1
CurrentIndex = 1
CVAvailableIndexes[CurrentIndex] = -1
Allocate...
CurrentIndex = 0
CVMaxIndex = 2
CurrentIndex = 2
CVAvailableIndexes[CurrentIndex] = -1
Allocate...
CurrentIndex = 0
CVMaxIndex = 3
CurrentIndex = 3
CVAvailableIndexes[CurrentIndex] = -1
Deallocate 3...
(CurrentIndex = 3)
CVAvailableIndexes[CurrentIndex (3)] = CVFreeIndex (0)
CVFreeIndex = 3
Allocate...
CurrentIndex = 3
CVFreeIndex = 0
CVAvailableIndexes[CurrentIndex] = -1
Deallocate 3...
(CurrentIndex = 3)
CVAvailableIndexes[CurrentIndex (3)] = CVFreeIndex (0)
CVFreeIndex = 3
Deallocate 1...
(CurrentIndex = 1)
CVAvailableIndexes[CurrentIndex (1)] = CVFreeIndex (3)
CVFreeIndex = 1
Allocate...
(CurrentIndex = 1)
CVFreeIndex = CVAvailableIndexes[CurrentIndex (1)] (3)
CVAvailableIndexes[CurrentIndex (1)] = -1
Allocate...
(CurrentIndex = 3)
CVFreeIndex = CVAvailableIndexes[CurrentIndex (3)] (0)
CVAvailableIndexes[CurrentIndex (3)] = -1
And so on...
CurrentIndex = 0
CVMaxIndex = 1
CurrentIndex = 1
CVAvailableIndexes[CurrentIndex] = -1
Allocate...
CurrentIndex = 0
CVMaxIndex = 2
CurrentIndex = 2
CVAvailableIndexes[CurrentIndex] = -1
Allocate...
CurrentIndex = 0
CVMaxIndex = 3
CurrentIndex = 3
CVAvailableIndexes[CurrentIndex] = -1
Deallocate 3...
(CurrentIndex = 3)
CVAvailableIndexes[CurrentIndex (3)] = CVFreeIndex (0)
CVFreeIndex = 3
Allocate...
CurrentIndex = 3
CVFreeIndex = 0
CVAvailableIndexes[CurrentIndex] = -1
Deallocate 3...
(CurrentIndex = 3)
CVAvailableIndexes[CurrentIndex (3)] = CVFreeIndex (0)
CVFreeIndex = 3
Deallocate 1...
(CurrentIndex = 1)
CVAvailableIndexes[CurrentIndex (1)] = CVFreeIndex (3)
CVFreeIndex = 1
Allocate...
(CurrentIndex = 1)
CVFreeIndex = CVAvailableIndexes[CurrentIndex (1)] (3)
CVAvailableIndexes[CurrentIndex (1)] = -1
Allocate...
(CurrentIndex = 3)
CVFreeIndex = CVAvailableIndexes[CurrentIndex (3)] (0)
CVAvailableIndexes[CurrentIndex (3)] = -1
And so on...
Further Application – An MUI Spell on a Timer
One of the largest multiinstanceability problems GUI tends to run into is timers, since you need globals or Jass systems to pass information through them. However, they are also fixed by simply applying this method to the spell.
Variables
Combined Systems Variables
Name | Type | Array? | Value |
CurrentIndex | Integer | No | 0 (Default) |
SPMaxIndex | Integer | No | 0 (Default) |
SPFreeIndex | Integer | No | 0 (Default) |
SPAvailableIndexes | Integer | Yes | N/A |
SPInstances | Integer | Yes | N/A |
We're going to have to add a few more variables to have this work right; otherwise, you lose a good deal of efficiency, among other things.
Spell-Related Variables
Name | Type | Array? | Value |
SPTimer | Timer | No | New Timer (Default) |
SPMaxInstance | Integer | No | 0 (Default) |
And then our custom spell data...
Custom Spell Data Variables
Name | Type | Array? | Value |
SPExampleArray | N/A | Yes | N/A |
Note: No variables are reusable between spells, except CurrentIndex. Theoretically the indexing variables (under Combined Systems Variables) could be reused, but there is a limit of 8190 instances per set of variables, so don't overuse them!
Code
Now, the spell will allocate the index with the first method, and then store it to its stack of instances with the second.
Note that you can remove the first method entirely, but this means you have to individually move every variable back upon freeing the instance (as seen in the deallocation section of the loop). This will cause deallocation to be a bit slower.
Only the Combined Systems example uses the Old System Variables variables, but is faster at deallocating.
The Lightweight System example is faster at allocating and a little less variable-heavy, but slower at deallocating. This is probably the ideal choice for your trigger, but it depends on personal preference (as spells with lots of fields can take ages to write the deallocators for this way).
How does the spell work? The spell focuses itself around one timer, rather than one per instance of the spell. Every time this timer expires, every instance of the spell will be executed (via looping through the array).
The deallocation is a bit odder. You take all of the values stored on the instance to deallocate, and move the values stored on the last instance to said instance. This way, the array shrinks in size without needing to move every value after the deallocated instance to the previous slot.
-
SpellCast
-
Events
- Unit - A Unit Starts the Effect of an Ability
-
Conditions
- (Ability Being Cast) Equal to <Some Spell>
-
Actions
- -------- Do other spell setup here --------
- Set CurrentIndex = SPFreeIndex
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- CurrentIndex Equal to 0
-
Then - Actions
- Set SPMaxIndex = (SPMaxIndex + 1)
- Set CurrentIndex = SPMaxIndex
-
Else - Index
- Set SPFreeIndex = SPAvailableIndexes[CurrentIndex]
-
If - Conditions
- Set SPAvailableIndexes[CurrentIndex] = -1
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- (SPMaxInstance Equal to 0)
-
Then - Actions
- Countdown Timer - Start SPTimer as a Repeating timer with timeout <spell refresh rate>
- Else - Actions
-
If - Conditions
- Set SPInstances[SPMaxInstance] = CurrentIndex
- -------- Allocate all spell arrays here using CurrentIndex, as done in the custom value examples, such as the following --------
- Set SPExampleArray[CurrentIndex] = <some value>
- Set SPMaxInstance = (SPMaxInstance + 1)
-
Events
-
SpellPeriodic
-
Events
- Time - SPTimer Expires
- Conditions
-
Actions
-
For Each (Integer A) from 1 to SPMaxInstance - 1, do Actions
-
Loop - Actions
- Set CurrentIndex = SPInstances[(Integer A)]
- -------- Do spell stuff here, with CurrentIndex being your index --------
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- -------- Ready to deallocate index --------
-
Then - Actions
- Set SPMaxInstance = SPMaxInstance – 1
- Set SPInstances[(Integer A)] = SPInstances[SPMaxInstance]
- Set SPAvailableIndexes[CurrentIndex] = SPFreeIndex
- Set SPFreeIndex = CurrentIndex
- -------- Make sure the newly placed index still gets hit this iteration --------
- Custom script: set bj_forLoopAIndex = bj_forLoopAIndex - 1
- Custom script: set bj_forLoopAIndexEnd = bj_forLoopAIndexEnd - 1
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- (SPMaxInstance Equal to 0)
-
Then - Actions
- Countdown Timer - Pause SPTimer
- Custom script: exitwhen true
- Else - Actions
-
If - Conditions
-
Else - Actions
- -------- You can put more spell stuff in the Else if you want it to only occur when the spell does not end --------
-
If - Conditions
- -------- You should NOT do any more spell stuff here --------
-
Loop - Actions
-
For Each (Integer A) from 1 to SPMaxInstance - 1, do Actions
-
Events
-
SpellCast
-
Events
- Unit - A Unit Starts the Effect of an Ability
-
Conditions
- (Ability Being Cast) Equal to <Some Spell>
-
Actions
- -------- Do other spell setup here --------
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- (SPMaxInstance Equal to 0)
-
Then - Actions
- Countdown Timer - Start SPTimer as a Repeating timer with timeout <spell refresh rate>
- Else - Actions
-
If - Conditions
- -------- Allocate all spell arrays here using SPMaxInstance, as done in the custom value examples, such as the following --------
- Set SPExampleArray[SPMaxInstance] = <some value>
- Set SPMaxInstance = (SPMaxInstance + 1)
-
Events
-
SpellPeriodic
-
Events
- Time - SPTimer Expires
- Conditions
-
Actions
-
For Each (Integer A) from 1 to SPMaxInstance - 1, do Actions
-
Loop - Actions
- -------- Do spell stuff here, with (Integer A) being your index --------
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- -------- Ready to deallocate index --------
-
Then - Actions
- Set SPMaxInstance = SPMaxInstance – 1
- -------- Move the array indexes to (Integer A) from SPMaxInstance, as done with the Custom Value examples, such as the following --------
- Set SPExampleArray[(Integer A)] = SPExampleArray[SPMaxInstance]
- -------- Make sure the newly placed index still gets hit this iteration --------
- Custom script: set bj_forLoopAIndex = bj_forLoopAIndex - 1
- Custom script: set bj_forLoopAIndexEnd = bj_forLoopAIndexEnd - 1
-
If (All Conditions are True) Then do (Then actions) Else do (Else actions)
-
If - Conditions
- (SPMaxInstance Equal to 0)
-
Then - Actions
- Countdown Timer - Pause SPTimer
- Custom script: exitwhen true
- Else - Actions
-
If - Conditions
-
Else - Actions
- -------- You can put more spell stuff in the Else if you want it to only occur when the spell does not end --------
-
If - Conditions
- -------- You should NOT do any more spell stuff here --------
-
Loop - Actions
-
For Each (Integer A) from 1 to SPMaxInstance - 1, do Actions
-
Events
Vexorian: Allocation/Deallocation algorithm.
DiscipleOfLife: For telling me to use it.
Alakon/Squiggy: For inspiring me to write this.
Several poor souls: For looking over it at my request.
DiscipleOfLife: For telling me to use it.
Alakon/Squiggy: For inspiring me to write this.
Several poor souls: For looking over it at my request.
Attachments
Last edited: