1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still haven't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. Rubbed the right way, the genie is out of its lamp! The 12th Concept Art Contest Results have been announced.
    Dismiss Notice
  4. Race against the odds and Reforge, Don't Refund. The 14th Techtree Contest has begun!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[GUI] Efficient Arrays - Multiinstanceabilty for Timers, Custom Values, and more

Discussion in 'Trigger (GUI) Editor Tutorials' started by PurplePoot, Aug 27, 2008.

  1. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,161
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3

    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]
        • 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>


    This may look a little weird at first, but it actually works quite well.

    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


    And that's it! The "custom values," in this case, are simply accessed by the array slots at the custom value of the subject unit.

    I don't get how the allocation algorithm works! (A runtime example of what might go on)
    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...


    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.

    Combined Systems
    • 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]
        • 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
        • 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)


    Now, since we don't have anything to directly attach the array values to, the periodic section of the spell can simply loop through them, as such:

    • 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
              • Else - Actions
                • -------- You can put more spell stuff in the Else if you want it to only occur when the spell does not end --------
            • -------- You should NOT do any more spell stuff here --------


    Lightweight System
    • 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
        • -------- 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)


    Now, since we don't have anything to directly attach the array values to, the periodic section of the spell can simply loop through them, as such:

    • 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
              • Else - Actions
                • -------- You can put more spell stuff in the Else if you want it to only occur when the spell does not end --------
            • -------- You should NOT do any more spell stuff here --------





    Credits
    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.
     

    Attached Files:

    Last edited: Sep 6, 2008
  2. Hawkwing

    Hawkwing

    Joined:
    Nov 28, 2006
    Messages:
    5,823
    Resources:
    28
    Models:
    15
    Icons:
    3
    Packs:
    1
    Skins:
    8
    Tutorials:
    1
    Resources:
    28
    Magnificent tutorial. It contains all the needed information, such as:

    • A brief overview of what the tutorial is about.
    • An explanation of the code.
    • The code it self.
    • A step by step guide on how to create this effect.
    • Very clear and easy to understand by any Triggerer, as long as that person has a minimal understanding of GUI, Variables, and the World Editor.
     
  3. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,161
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3
    ~Reorganized one of the triggers and fixed a bug in the deallocation example for timers.
     
  4. Kwah

    Kwah

    Joined:
    May 9, 2007
    Messages:
    3,391
    Resources:
    8
    Icons:
    5
    Maps:
    1
    Tutorials:
    2
    Resources:
    8
    WAIT!

    Poot, made. . . GUI?

    It looks nice though, very nice.
     
  5. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,161
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3
    I got tired of seeing non-MUI GUI, and enough people asked related questions.

    That's the same reason I tend to do a lot of things (OrderQueue was due to enough people asking how to do it)... Meh.
     
  6. DiscipleOfLife

    DiscipleOfLife

    Joined:
    Mar 25, 2005
    Messages:
    252
    Resources:
    1
    Icons:
    1
    Resources:
    1
    Why don't you use the allocation algorithm jasshelper uses for structs for your unit indexes? It is faster than what you have here and you wouldn't have to transfer all the data you have stored to CVMaxIndex around in deallocate and also then one could use variables to hold units' indexes without having to worry about the units' indexes changing due to some other index being deallocated.
     
  7. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,161
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3
    I believe it should be faster for deallocation but slower for allocation, for the reason below.

    I guess the main reason that I didn't use that algorithm is that this point didn't come to mind.

    This seems unlikely to me (if worst comes to worst, just call GetUnitUserData again), but given the above advantage, I'll most likely switch the algorithm anyways.

    EDIT: Whee, updated to vJass allocators.

    EDIT2: Fixed an annoying bug caused by how badly For loops are implemented in GUI.

    EDIT3: Demo map for the Lightweight System for spells is now available.

    EDIT4: Demo map for the Combined Systems for spells is now available.
     
    Last edited: Aug 30, 2008
  8. -Berz-

    -Berz-

    Joined:
    Mar 5, 2008
    Messages:
    3,203
    Resources:
    123
    Icons:
    111
    Packs:
    1
    Skins:
    1
    Spells:
    10
    Resources:
    123
    Thanks man I'll try it out!
     
  9. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,161
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3
    Added demo map for Custom Values.