• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

What happens to structs internally?

Status
Not open for further replies.
Level 22
Joined
Feb 6, 2014
Messages
2,466
Hello everyone. I want to learn how structs internally operates.

After creating structs and looking at the actual script generated by JassHelper, this is the function it generates (struct name is myStruct)

JASS:
//Generated allocator of myStruct
function s__myStruct__allocate takes nothing returns integer
    local integer this=si__myStruct_F
    if (this!=0) then
        set si__myStruct_F=si__myStruct_V[this]

    else
        set si__myStruct_I=si__myStruct_I+1
        set this=si__myStruct_I
    endif
    if (this>8190) then
        return 0
    endif
    set si__myStruct_V[this]=-1
    return this
endfunction

//Generated destructor of myStruct
function s__myStruct_deallocate takes integer this returns nothing
    if this==null then
        return
    elseif (si__myStruct_V[this]!=-1) then
        return
    endif
    
    set si__myStruct_V[this]=si__myStruct_F
    set si__myStruct_F=this
endfunction

Correct me if I'm wrong, but from what I see, the this integer is the instance/index of the struct data. However, I can't figure out what is the si__myStruct_V[this] and si__myStruct_F for? Does the allocation and deallocation operate like this?
JASS:
8     4     2     9     7           this = 5
8    ---    2     9     7           this = 5
8     7     2     9                  this = 4
 
I'll break it down a bit.

Struct allocation behaves like a stack. (you can visualize it as one of those toys where you stack rings on a pipe):
fisher-price-rock-a-stack-400x400-imadjz4xcmnabgjg.jpeg


Before I go into the details, let me define what an instance is. Creating a new instance is equivalent to calling .allocate() or .create() for your struct. It is just creating a new index for you to use (struct members are internally stored as arrays, so .create() and .allocate() just returns an available index for you to use).

Next, let me go into the allocation process. First, it'll check if there are any available instances that can be reused. If there aren't any, it'll extend the cap by 1 and give you that new instance. It'll mark the instance as used, and then return that instance.

Let's break down the variables:
Instance Count
This is represented as si__myStruct_I, and it counts the total
number of instances that were allocated.​
Free Index
This is represented as si_myStruct_F, and it points to the most recently freed instance.​
Stored Instances
This is represented as si_myStruct_V, and it keeps track of existing instances.
Instances in use return -1. Otherwise, it points to an available index.​

Let's go back to our toy example to really visualize what is going on. We'll be creating a series of instances and observing what happens with all the variables that we are using.
  • We'll be starting off with an empty struct. We haven't created anything yet.
    JASS:
    struct A
    endstruct
    attachment.php
  • Let's create 1 instance:
    JASS:
    set instances[1] = A.create()
    Observe what the code is reduced to:
    The allocation function looks pretty messy. But given the initial picture above, what do you think this function will simplify to?
    JASS:
    function s__myStruct__allocate takes nothing returns integer
        local integer this=si__myStruct_F // == 0
        if (this!=0) then
            set si__myStruct_F=si__myStruct_V[this]
    
        else
            set si__myStruct_I=si__myStruct_I+1 // I = 1
            set this=si__myStruct_I // this = 1
        endif
        if (this>8190) then
            return 0
        endif
        set si__myStruct_V[this]=-1 // V[1] = -1
        return this // return 1
    endfunction
    As shown by the comments, this is what it ultimately ends up doing on our first creation.
    JASS:
    function allocate takes nothing returns integer
        local integer this = 0
        set I = I + 1
        set this = I
        set V[this] = -1
        return this
    endfunction
    The struct didn't have any instances, so naturally there aren't any "free" instances yet. Instances are only considered "free" if they have been allocated and deallocated. As such, we raise our instance count (I = I + 1) and return that number (1).

    We created a new instance "1" for the user to use. It is simply placed on the side and given to the user. I've labeled it as "1" below.
    attachment.php

    In "V", I have labeled the item as light blue. From now on, that will signify that the item is marked as used. In the code, it is equivalent to setting it to -1.
  • Let's create two more instances.
    JASS:
    set instances[2] = A.create()
    set instances[3] = A.create()
    Similar to before--we don't have any "freed" instances yet. F still points to 0. As such, "I" will go up and we will simply give those instances to the user:
    attachment.php

    Keep in mind that V isn't actually equal to an array of [1, 2, 3]. That just represents the indices and which are marked. It actually looks like:
    V[1] = -1
    V[2] = -1
    V[3] = -1
    This shows that all three of those instances are in use.
  • Now let's try deallocating the instance "1".
    JASS:
    call instances[1].destroy()
    When we deallocate an instance, there are some preliminary checks to make sure we don't run into issues:
    (1) Is the instance null? If so, we should do nothing.
    (2) Is the instance not in use? If so, that means it was already deallocated, so the user probably did something wrong. Wc3 will print these as a "double-free" error if you have "Jasshelper -> Debug Mode" enabled.

    If those checks are passed, then it will set V[this] to point to F. In a way, you are kind of "storing" this value for later. Don't worry about it now, it'll be clear in the next example.

    In the next line, we set F (the last freed instance) to point to our deallocated instance, so that it can be reused. :)
    JASS:
    function s__myStruct_deallocate takes integer this returns nothing
        if this==null then
            return
        elseif (si__myStruct_V[this]!=-1) then
            return
        endif
        
        set si__myStruct_V[this]=si__myStruct_F
        set si__myStruct_F=this
    endfunction
    This would end up looking like this (for instance 1):
    JASS:
    set V[1] = F
    set F = 1

    This picture will be a lot to grasp, so bear with me.
    attachment.php

    Ever since I made this image, you were probably wondering what that weird pole was on the left side. That will represent our "free" instances. When we allocate a new instance, we'll first check if there are any instances available from that pole. Otherwise we'll increase the instance count by 1 and make a brand new one. In our case, we moved 1 onto that pole since it is destroyed--it is now available for use on the next allocation.

    As such, F now points to 1 (the top of the pole "stack"). V changed too--1 is no longer in use, so it isn't marked as blue. Instead, the old value of F took its place, which is why a put a 0 there.
  • If you are confused, don't worry. Let's keep moving by deallocating instance #2.
    JASS:
    call instances[2].destroy()
    Same process as before.
    attachment.php

    Since we freed instance #2, we put it on top of the stack. And since F is supposed to point to the most recently freed instance, we set it to point to 2. But what will happen to instance 1? We want to keep track of it somewhere, so we set V[2] = 1, just like how we set V[1] = 0 in the previous example.

    What does this mean? Well, if we were to call .allocate() right now, it would grab 2 from the top. What else do you think would have to be done? We would have to mark "2" as "used" (mark it as blue in V), and we would have to set F to 1. We'll explore this in the next example.
  • Let's allocate a new instance to finish off.
    JASS:
    set instances[2] = A.create()
    attachment.php

    Notice anything familiar? This is exactly where we were 2 steps ago! When we allocated, it checked if there were any free instances on the stack. It grabbed 2 from the top, reassigned the "top" pointer to be 1, and set 2 as used. Hopefully this is expected behavior by now.
  • Just to make things absolutely clear, I'll deallocate 3 as well.
    JASS:
    call instances[3].destroy()
    attachment.php

    As you can see, we put 3 on top of the stack instead. V[3] was changed to point to 1 (the freed index below it), F was changed to 3 since that is the new top, and we're done!

Go through these examples and let me know if it is still confusing. I know it is a big wall of text, but hopefully the images will help you visualize the process in your head. :) Good luck.
 

Attachments

  • stack1.png
    stack1.png
    17.9 KB · Views: 172
  • stack2.png
    stack2.png
    16.6 KB · Views: 169
  • stack3.png
    stack3.png
    20.8 KB · Views: 167
  • stack4.png
    stack4.png
    21 KB · Views: 297
  • stack5.png
    stack5.png
    24.5 KB · Views: 165
  • stack6.png
    stack6.png
    22.3 KB · Views: 166
Status
Not open for further replies.
Top