Trying to create a StringMap (OBSE array)

Post » Thu Jan 27, 2011 9:42 am

I've been trying to create a StringMap. The construct is fine, but when I try to dump the contents of the array, it's empty.

First, here's the code:
let summons := ar_Construct StringMaplet k := 4ForEach each <- (actor.GetActiveEffectCodes)	let index := each["value"]	if (MagicEffectUsesCreatureC index)		let effectKey := each["key"]		let arrayNPC[i][k] := actor.GetNthActiveEffectSummonRef effectKey		let xRef1 := arrayNPC[i][0]		set sKey to sv_Construct "%i" xRef1		let xRef2 := arrayNPC[i][k]		set sValue to sv_Construct "%i" xRef2		scribe "Key: %z, Value: %z" sKey sValue 0		let summons[sKey] := sValue		let k := k + 1	endifLoop


This is only a snippet of the code. This whole thing is inside another loop. I'm using Conscribe to list the values for sKey and sValue and they are what I want - they are being created properly. So when populating a StringMap, can't I simply add an element with "let summons[sKey] := sValue"? Or is it a problem with scope? In other words, the contents of the summons array don't persist outside of the ForEach loop?

Here is what the scribe command lists:

Key: FF012336, Value: 00000000
Key: FF012336, Value: FF012344
Key: FF012336, Value: FF012340
Key: FF012336, Value: FF012344
Key: FF012336, Value: FF012340

which is correct. So I want the StringMap to look like:

summons[FF012336] = FF012344
summons[FF012336] = FF012340

etc.

Thanks in advance...
User avatar
TASTY TRACY
 
Posts: 3282
Joined: Thu Jun 22, 2006 7:11 pm

Post » Thu Jan 27, 2011 4:24 am

I believe the "let summons[skey] := svalue" statement will only work if there is an element that already has the skey er...value as its key. I.e. it's going to be trying to find the "skey" element in the array and overwrite it. I imagine you will have to set up the keys firt, by treating it as a Map, so you can assign the key to the n'th element, after which the keys are available for the lookup and thus the value assignment can be done. But exactly how that's done is not clear to me either.
User avatar
Taylrea Teodor
 
Posts: 3378
Joined: Sat Nov 18, 2006 12:20 am

Post » Thu Jan 27, 2011 6:13 am

Yes, it would kind of defeat the purpose wouldn't it? I wasn't sure if this was an OBSE bug or not, so I thought I'd post a new thread first... I don't know what the keys are going to be, so I can't create them ahead of time. With regular arrays, you just define the new element, so I have to believe this is a bug...
User avatar
Mario Alcantar
 
Posts: 3416
Joined: Sat Aug 18, 2007 8:26 am

Post » Thu Jan 27, 2011 4:15 am

Yes, it would kind of defeat the purpose wouldn't it? I wasn't sure if this was an OBSE bug or not, so I thought I'd post a new thread first... I don't know what the keys are going to be, so I can't create them ahead of time. With regular arrays, you just define the new element, so I have to believe this is a bug...

I don't think it's a bug, but the documentation does lack any info on how to initialize. Most uses of StringMap are to receive results of "GetBasketOfValues" requests that tell you everything about an object or actor, so the keys are pre-defined by the function you're calling to fill it (which is NOT a piece of script, but a C++(I think) method inside OBSE that is privy to the secret handshake!)

But if you don't know the keys in advance, then probably a StringMap isn't the right storage anyway. If you just want to be able to associate two sets of values, then two arrays and a matching numeric index works just as well. Iterate through the "skey" Array and use the same index value to access the "svalue" array as you go. The StringMap is only useful if you want to go directly to the value for a known key without any iterative search, and you'd only be doing that with a known-in-advance key, probably coded right into the script.
User avatar
Danel
 
Posts: 3417
Joined: Tue Feb 27, 2007 8:35 pm

Post » Thu Jan 27, 2011 12:16 pm

Well, I was following http://cs.elderscrolls.com/constwiki/index.php/Introduction_to_OBSE_arrays as a guide. That seems to indicate you just add elements to the array as you would any other. I was hoping to use a StringMap for performance and ease of use reasons. What I really want is a hashmap (from java), and it appeared that a StringMap was the closest OBSE had. I could certainly create a two dimensional array - arrayNPC in the code snippet above is two dimensional, but a StringMap would be more efficient if I can use it in this fashion. The primer on OBSE arrays (the page I linked to previously) seems to indicate that a StringMap would work, but documentation is sparse!
User avatar
Rachel Hall
 
Posts: 3396
Joined: Thu Jun 22, 2006 3:41 pm

Post » Thu Jan 27, 2011 3:25 am

Ok, I just did some testing and this works:

scn aaaTestarray_var arrbegin gamemodelet arr := ar_Construct StringMaplet arr["element1"] := "value1"let arr["element2"] := "value2"ar_Dump arrend


ar_dump yields:
[ element1 ] : value1
[ element2 ] : value2

So there's no problem creating a StringMap and adding elements via the usual method of assigning keys and values. So, I'm starting to suspect it's either a timing issue in the ForEach loop or a scope issue - i.e. the array is undefined outside of the ForEach loop. Now I know that that would be the case for the "each" array I'm using in the ForEach loop, but the summons array is defined outside of the loop.

Any ideas?
User avatar
Krista Belle Davis
 
Posts: 3405
Joined: Tue Aug 22, 2006 3:00 am

Post » Thu Jan 27, 2011 7:42 am

I'm fairly certain that Oblivion script, OBSE or not, does not really have "scope" beyond the obvious (script-scope and global-scope). The issue to me seems to be that you're not initializing the sub-arrays. You would typically use something like this:
let arr := ar_Construct StringMapforeach i <- some_array    let arr[*i] := ar_Construct StringMap    foreach j <- some_other_array        let arr[*i][*j] := whatever    looploop

User avatar
April
 
Posts: 3479
Joined: Tue Jun 20, 2006 1:33 am

Post » Thu Jan 27, 2011 2:25 pm

With that output from the previous line, the "let summons[sKey] := sValue" is creating the array elements as you intended.
But note that, as all the keys seem to be equal, each iteraction will overwrite the same element.

Are you sure the array is empty
User avatar
Sarah Unwin
 
Posts: 3413
Joined: Tue Aug 01, 2006 10:31 pm

Post » Thu Jan 27, 2011 4:18 am

@DragoonWraith: my summons array is only a single dimension, so I don't need the second declaration. Another term for what I'm trying to create would be an associative array - not sure if that helps describe what I'm trying to do or not. Basically I only want a single array with strings as the keys instead of numbers.

Anyway the net result should be:

[sKey] : sValue

if I was to list it the way ar_dump would.

@QQuix: Yes I'm pretty sure the array is empty. Now you do raise a good point, which might also link back to what DragoonWraith was trying to say: I might need a multi-dimensional array here. Bascially each summoner can have multiple summons (I've got Supreme Magicka loaded, so you can have multiple summoned critters). So as I have the data listed in my original post, I would want one this kind of structure:

summons[FF012336][FF012344] = dummy value
summons[FF012336][FF012340] = dummy value

Since it's one summoner with multiple critters, both of those values would have to make up the key and I would really not have a value at all. Does that make sense? I'm surprised the summons array still wound up being empty with my previous code though. I didn't list all of the code, but I was doing an array dump after the loop, and it was reporting the array as being empty.
User avatar
Felix Walde
 
Posts: 3333
Joined: Sat Jun 02, 2007 4:50 pm

Post » Thu Jan 27, 2011 4:25 pm

The problem with using formID strings as StringMap keys is that when the player's load order changes, your map keys no longer match the records they're supposed to match.

Storing formIDs as values works as expected, with load-order correction and values being automatically set to zero when the mod is removed, so unless these StringMaps are used only within the context of a single game session, I'd recommend using a pair of parallel arrays instead, with one storing the ID and the other storing your value for it. Then, after a reload (check with GetGameLoaded) you'd go through the two arrays and remove any entries whose ID is zero (meaning that its source mod is no longer loaded).
User avatar
lolly13
 
Posts: 3349
Joined: Tue Jul 25, 2006 11:36 am

Post » Thu Jan 27, 2011 2:49 pm

Oh, I don't need any persistence with the keys. In fact the arrays will be destroyed every time the script runs :) As I said, this is only a snippet of the code.

After the latest discussion, I realized that I would actually be better off using the form ID of the summoned critter as the key. This also lines up better with the purpose of populating this array for use in the rest of the script. However, now that I got my debugging working better (I'm having some issues with Conscribe :(), I see that I'm getting an error when I try to assign the array elements. I really don't understand what the problem is.

Here's the new code:
ForEach each <- (actor.GetActiveEffectCodes)	let index := each["value"]	if (MagicEffectUsesCreatureC index)		let effectKey := each["key"]		let arrayNPC[i][k] := actor.GetNthActiveEffectSummonRef effectKey		let xRef1 := arrayNPC[i][k]		set sKey to sv_Construct "%i" xRef1		let xRef2 := arrayNPC[i][0]		set sValue to sv_Construct "%i" xRef2		scribe "Key: %z, Value: %z" sKey sValue 0		let summons[sKey] := sValue		let k := k + 1	endifLoop


So the resulting array should look like this:

Key: FF01233D, Value: FF012336
Key: FF012343, Value: FF012336
Key: FF01234A, Value: FF012337
Key: FF012343, Value: FF012336

So we've got two summoners here, one with two critters. Now I still have a duplicate key, but if that is overwritten, that's fine. However if that causes an error, then I will have to detect the existence of the key before attempting to create the new element.

Anyway, the error I'm getting is:
Error in script 5e00fddaInvalid array access - the array was not initialized.    File: My Plugin.esp Offset: 0x0DE5 Command: LetError in script 5e00fddaOperator [ failed to evaluate to a valid result    File: My Plugin.esp Offset: 0x0DE5 Command: Let


I have the "let summons := ar_Construct StringMap" statement elsewhere in the script. All the rest of the variables are also declared elsewhere. So any more ideas? How do I convert that offset in the error message to a position in the script?
User avatar
Jason Wolf
 
Posts: 3390
Joined: Sun Jun 17, 2007 7:30 am

Post » Thu Jan 27, 2011 5:16 am

Use Scruggs's Script Viewer to convert to the offset.
User avatar
Russell Davies
 
Posts: 3429
Joined: Wed Nov 07, 2007 5:01 am

Post » Thu Jan 27, 2011 1:51 am

I think the real issue here lies somewhere different.
According to the documentation (and if memory serves me right, it really did act so in tests) the keys of a StringMap don't need to be in parantheses. They're treated paranthesized automatically.
So a call like "let summons[sKey] := sValue" doesn't assign index "" the value of sValue but instead literally does the same as 'let summons["sKey"] := sValue' and now the issue should be too obvious. There will only be 1 index, "sKey". But I have to admit, the explanation for the array being reported as "empty" is beyond me.

It's been ages already now since I last scripted something, but wasn't there a way to make the [...] use the "contents" of the variable as key-string instead of its "name"? I could swear there was something back then...

edit: Just had a look at my old scripts to find this way I was talking of... but I actually do have lines where I use a string_var as an array key and it does use the value not the name of the variable... so now I'm a little confused.

Uh, never mind. I seem to be too sleepy already. I think my memory didn't serve me right anymore.
User avatar
Beast Attire
 
Posts: 3456
Joined: Tue Oct 09, 2007 5:33 am

Post » Thu Jan 27, 2011 10:11 am

Thanks for the responses, guys. I did find that one section of my script was not executing as expected, so I will continue to debug and move some things around. I'll also see if I can dig up Scrugg's viewer. I've actually been looking at the OBSE source code :blink: I'm afraid it has been many years since C++ for me - and now I remember why I switched to java - pointers :eek:

Edit: could someone provide a link to Scruggs' script viewer? I've tried some searches and clicked on the links in his sig and can't find it, so I must be looking for the wrong thing...
User avatar
Heather Stewart
 
Posts: 3525
Joined: Thu Aug 10, 2006 11:04 pm

Post » Thu Jan 27, 2011 11:04 am

http://www.gamesas.com/index.php?/topic/1047306-beta-oblivion-script-extender-obse-0018/page__view__findpost__p__15199347


Drake, I think the problem you half remember is, actually, the other way around: if you want to use the string "sKey" as the key, don't use the parenthesis and happen to have a variable with that name (or anything the compiler recognizes as a valid keyword, like apple or Martin), you end up using the variable contents as the key.
User avatar
Andrew Lang
 
Posts: 3489
Joined: Thu Oct 11, 2007 8:50 pm

Post » Thu Jan 27, 2011 10:14 am

Oiy! Thanks for the link - don't think I was going to find that on my own ;)

I've partially solved this! I actually have several nested loops and I need to find the right spot to construct the array. So I'm not getting the error anymore and the array is being populated. Now to see if I can get the rest of the script working...

Even in the original code snippet, all that code was inside a loop, so the construct statement was in the wrong location :wacko: I think I'm going loopy...
User avatar
Mark Hepworth
 
Posts: 3490
Joined: Wed Jul 11, 2007 1:51 pm


Return to IV - Oblivion