Jump to content

Tutorial: Nvse4+ Part 4: Array Variables


Recommended Posts

  • 4 weeks later...

To test your fear that arrays do not get cleaned up without Ar_Null, I wrote a test plugin which created a big array full of strings via UDF once per frame:

 

 

 

scn CreateBloat

array_var arr
int i

Begin Function { }

    let arr := Ar_List "GARBAGE BE HERE", "GARBAGE, GARBAGE, GARBAGE", "AND MORE AND MORE"
    while (i += 1) < (GetNumLoadedMods)
        Ar_Append arr, (GetNthModName i)
    loop

End

---
scn BloatQuestScript

; once per frame!
int i

Begin GameMode

    call CreateBloat
    let i += 1
    Print "BLOAT multiplied by " + $i + "!!!"

End

 

 

 

I made 2 save games, one after 4150 calls, and one after 8181 calls. They were both identical and 1656 bytes. Also poked around with Ar_DumpID and could find no evidence of 8181 arrays lying around.

 

Then I decided to check object (token) scripts, adding one of these tokens to the player every frame:

 

 

scn BloatTokenScript

array_var arr

Begin OnAdd

    let arr := GetLoadedTypeArray 42 ; # Every NPC form from every plugin, 4507 total in my case

    RemoveMe

End

 

 

 

I let it run for ~1300 frames, then saved (which took a long time), the nvs file was 70.1 Megabytes. GetItemCount for the token showed 0, but random poking with Ar_DumpID showed all those NPC arrays still existed. I tried re-saving after both loading and restarting game, but it did not clean them up.

 

I then added "let arr := Ar_Null" to the object script, and this solved the issue.

 

---

Conclusion: You do not need to use Ar_Null in UDF scripts, but you definitely do need to use it in other object scripts, and probably Effect too (untested).

Link to comment

That's definitely good to know, thanks Odessa.

 

Feel like doing the same thing regarding strings and sv_construct / sv_destruct? I believe there is a similar leakage problem there. I've been going nuts with ar_null and sv_destruct since Doc happened upon the issue. Bodymorph was making massive NVSE files as well, so I just started clearing them all and testing. Any clears that caused a crash, I removed, and left the rest in. ;)

Link to comment
  • 2 weeks later...
Guest tomm434

DoctsSax, in tutorial you say this about populating maps and stringmaps arrays much quickier

Okay, but, ehm, what if we have more than 20 elements to add, or simply don't know how many we'll add?  

And then nothing =)

 

So how can I populate maps array if I don't know how many references will I add to it? (in other words how do I populate maps array without "Ar_Map" command?)

Link to comment
Guest tomm434

yep Ar_append works for regular arrays. But what if I want to create map array for NPC and give each of them special key

9.5        "SunnyRef"
10.1      "PeteRef"
13.5      "GeckoManRef"
15.6      "PlayerRef"

How Can I add another pair (number+reference) without Ar_map ?

Link to comment

DoctsSax, in tutorial you say this about populating maps and stringmaps arrays much quickier

Okay, but, ehm, what if we have more than 20 elements to add, or simply don't know how many we'll add?  

And then nothing =)

 

So how can I populate maps array if I don't know how many references will I add to it? (in other words how do I populate maps array without "Ar_Map" command?)

 

Ah yeah, that was more of a segue into talking about loops. The idea would be that for instance you ar_map 2 maps, then use a loop to add the elements of one to another. If the keys or values aren't set beforehand you do as pride says there.

Link to comment
  • 2 weeks later...

If you erase an element from an array from within a foreach loop when that element is the one under iteration, that will most likely break the loop. With regular arrays, it doesn't always, but it does if you for instance pass the array as parameter to a UDF from within the loop. It'll always break with maps and stringmaps. The thing to do here is do your erasing when the loop is done, by for instance adding elements to erase to a new array during the loop and erase them from the original afterwards, or add the elements to keep to a new array and let the old one be a copy of that one afterwards.

There is an alternative approach that can be faster and perhaps not as confusing to people trying to iterate through an array (maps and stringmaps only!) and remove things during the iteration. If you use ar_keys you can iterate through that instead and safely delete from the array you want to.

 

This is unsafe:

foreach item <- arMyArray
  if <somecondition>
    ar_erase arMyArray item["key"]
  endif
loop
But this is nearly identical (two extra lines, one changed line) and is safe

let keys := ar_Keys arMyArray
foreach item <- keys
  if <somecondition>
    ar_erase arMyArray item["value"]
  endif
loop
let keys := ar_Null

NOTE: you can only do this with maps and stringmaps, not with normal arrays. It will break just like the unsafe way when used on a normal array.

Link to comment
Guest tomm434

yes I noticed that if you remove elements during foreach the first way  - it doesn't break the loop completely but it doesn't work as intended - some elements are skipped.

 

Link to comment

That's normal behavior for arrays and why doc says to not do that in the tutorial. If you have an array like [0,1,2,3,4] and you're in a for or foreach loop on 2 and delete it, the next one is going to be 4 and 3 will be skipped. You can get around this with normal arrays (not maps or stringmaps) by counting downward instead of upward.

 

I'm going to edit that post now because I didn't think of normal arrays like that, which won't work correctly even using ar_keys -- that works only with maps and stringmaps which are mainly what I use.

Link to comment

I should point out that with regular arrays erasing during a loop can work out pretty well automatically, but better safe than sorry. For maps & stringmaps, the ar_keys method looks pretty handy.

I haven't had a problem doing it this way as long as I count backwards.. e.g. from ar_size downto 0. That way if I delete 6, it doesn't matter if 7...10 get reindexed. When you count upward, that's when you can get screwed up.

Link to comment

Here's a handy 2 liner to loop through a regular array and delete all elements matching a certain condition:

while (Ar_Erase My_Array, i + (Ar_BadNumericIndex * eval !(SomeCondition))) || TestExpr My_Array[i += 1 * SomeCondition]
loop

(You'll have to reset i to 0 if you want to repeat it)

 

:P

Link to comment
Guest tomm434

Odessa, how do you I that? What if I want to delete all actors who are males? How do I specify the reference in "* eval SomeCondition" part?

Link to comment

It was a joke, although it should work. I wouldn't recommend using something that looks so obscure.

 

TestExpr returns false if an NVSE error is generated. Ar_Erase returns the count removed, (0 if you call it with a bad index, which is the case if the condition is met)

 

 

GetIsSex needs a ref var, so I think you'd need the uglier looking:

while (TestExpr MyRef := My_Array[i]) || Ar_Erase My_Array, (i + (Ar_BadNumericIndex * eval !(MyRef.GetIsSex "Male"))) || TestExpr My_Array[i += 1 * MyRef.GetIsSex "Male"]
loop

Might need to adjust the parentheses to get it to assign variables in right order.

 

 

Link to comment
  • 1 month later...
Guest tomm434

Any way to check if particular dimensional array is constructed?

 

 

There is a multidimensional array

 

aaSCMainQuest.SlaveList[0]

aaSCMainQuest.SlaveList[1]

aaSCMainQuest.SlaveList[2]

 

I need to check if aaSCMainQuest.SlaveList[0] is constructed.

if I do simple check

if aaSCMainQuest.SlaveList[0]
endif

It doesn't compile (no such variable)

 

When I  did this code (in hope that Ar_size will return -1 for array that is not initialized)

let Size := Ar_size aaSCMainQuest.SlaveList[0]

it throws error in console that array is not initialized (error in script etcs..). Makes reading debug log much harder.

 

 

 

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. For more information, see our Privacy Policy & Terms of Use