Jump to content
Sign in to follow this  
jwllorens

dynamically access an nested array index path?

Recommended Posts

Is there a way to dynamically set an array element in a multidimensional array?

 

 

 

For example, I have the following array:

 

_myArray = ["hello",["my","name","is"],["frank",["and","i","like",["chicken"]]]]

 

I know the structure of this array, but I want to be able to use one function to set any of the array elements by calling the function in this way:

 

[_newValue,_elementPath] call fnc_setElement;

 

so, I could do something like [_myArray,"hamburgers",[2,1,3,0]] call fnc_setElement; and the result would be:

 

(_myArray == ["hello",["my","name","is"],["frank",["and","i","like",["hamburgers]]]])   //true

 

or I could do  [_myArray,"hate",[2,1,2]] call fnc_setElement; and the result would be:

 

(_myArray == ["hello",["my","name","is"],["frank",["and","i","hate",["chicken]]]])    //true

 

 

 

Now, I can do this by creating a string dynamically and then compiling it fairly easily, but I need this function to be fast so I don't want to compile at runtime.  Is there a better way to do this that doesn't compile a string?

 

 

 

Edit: I think I have an idea, but I can't test it right now.  It depends on this question though:

 

_arr = ["lonleyData"];

_data = (_arr select 0) select 0;

 

What happens if I do that?

Share this post


Link to post
Share on other sites


fnc_setElement =

{

    params ["_array", "_path", "_value"];

    private _index = _path deleteAt (count _path - 1);

    {_array = _array select _x} forEach _path;

    _array set [_index, _value];

};

arr = [[1,2,3],[4,5,6],[7,[8,9]]];

[arr, [2,1,1], 100500] call fnc_setElement;

hint str arr; //[[1,2,3],[4,5,6],[7,[8,100500]]]

Share this post


Link to post
Share on other sites

Very slick.  Thank you.  Now I am trying to expand on this to see if I can set multiple values within one function.  However, I am a little confused as to what is happening with "arr" and "_array".

 

"_array" is a new variable initialized when the function is called that points to the same array as "arr."  It seems like this is important, because the function works with "_array" and adjusts it so that it points to the deepest array within "arr" as specified by the _path, before calling set to adjust a value within _array.  Since _array points to the original array, "arr" is therefore updated with the new value but not effected by the forEach loop which turns _array into a one-dimensional array.

 

However, what if I wanted to define "arr" within the function, rather than passing it as a parameter to the function?  I assume "arr" would be defined in the function, and "_array" would need to reference that same array "arr" but not copy it.

 

So would this work?

 

 

fnc_setAreaData = {

[
	_this,
	{		
		params ["_area","_data"];
                _varName = ("JWL_areaData_" + _area + JWL_KeyCode);
		_pArray = missionNameSpace getVariable _varName;
		{
			_tArray = _pArray;
			_value = _x select 0;
			_path = _x select 1;
			_index = _path deleteAt ((count _path) - 1);
			{_tArray = _tArray select _x} count _path;
			_tArray set [_index, _value];
		} count _data;
		missionNameSpace setVariable [_varName,_pArray,false];
	}
] execFSM "unscheduledCall.fsm";

};

That is using your call.fsm from your blog, just renamed so it is more clear what it is.  I want to ensure that the variable is retrieved and set before any other scripts try to do the same, since different scripts are responsible for adjusting different elements in the global variable and I don't want them conflicting and overwriting changes made by a different script due to execution order.  So that is why I am wrapping it all in your FSM that will execute the code in an unscheduled environment.

 

I would hope this would do the following:

hint str (missionNameSpace getVariable "JWL_areaData_LumberYard_9238001265");
//["Lumber Yard",JWL_areaFlag_LumberYard,WEST,true,[[JWL_areaResp_LumberYard_0,JWL_areaResp_LumberYard_1],[[WEST,2],[WEST,3]]]]

_newSide = EAST
_respInd1 = [EAST,4]
_respInd2 = [EAST,5]
["LumberYard",[ [_newSide,[2]] , [_respInd1,[4,1,0]] , [_respInd2,[4,1,1]] ]] call JWL_fnc_setAreaData;

hint str (missionNameSpace getVariable "JWL_areaData_LumberYard_9238001265");
//["Lumber Yard",JWL_areaFlag_LumberYard,EAST,true,[[JWL_areaResp_LumberYard_0,JWL_areaResp_LumberYard_1],[[EAST,4],[EAST,5]]]]

Share this post


Link to post
Share on other sites

Interesting, I should dig around the BIS functions more often.  

 

I wish they would upload the code to the wiki, even if it is out of date most of the time.  Could be helpful to learn from when access to the in-game function viewer is not readily available.  Regardless, I don't know why, but I guess I like re-inventing the wheel.  I'd rather have a more purpose-built function for my needs and eliminate excess.  

 

For example, the only time I forsee needing to set an element in a nested array in my mission is when updating information about a specific area.  Since I have decided to create unique variables on the server for each location (they contain information crucial to winning or losing the mission) I find that it is easiest to access them with getVariable and setVariable and a custom string.  These server-local global variables will have somewhat randomized names to protect against a client using publicVariable to damage the data.  So for this function I would want to provide a dynamically constructed variable name using the server-local keycode among other properties, then an array of ["value",[path]]'s to update.  This could be anything from respawn indices to remove upon getting the information and re-setting those indices to new respawn indices, to getting and setting information about AI groups that are linked to the location through the location data variable in missionNamespace.  

 

I found that this works quite well, thanks to KK's insight:

//_varName is name of missionNamespace variable to alter (string)
//_data is an array consisting of [_value,_path]
//     _value is the new value to set at...
//     _path, which is an array that defines the path to the value location in the array eg. [3,2,1]
//                  eg. (((_var select 3) select 2) select 1)


[
	_this,
	{
		params ["_varName","_data"];
		_pArray = missionNameSpace getVariable _varName;
		{
			_tArray = _pArray;
			_value = _x select 0;
			_path = _x select 1;
			_index = _path deleteAt ((count _path) - 1);
			{_tArray = _tArray select _x} count _path;
			_tArray set [_index, _value];
		} count _data;
		missionNameSpace setVariable [_varName,_pArray,false];
	}
] execFSM "functions\unscheduledCall.fsm";

The change is not immediate, there is a delay before the update, but it does appear to be... what is the word... well, it gets the missionNamespace array and then sets it with the new element(s) and at least from my tests so far, wont be interrupted.  I don't want other scripts calling getVariable, then updating an element, then setVariable in between the getVariable and setVariable of another script because that would result in overriding the changes made by the first script.

 

And as I mentioned before, it doesn't do anything else, there is no fluff.  I am not a huge fan of "universally applicable" functions.  Call me anal, but I want to know exactly what my functions are doing, I want to know that they are doing what I want them to, and I don't want them to do any unnecessary work.  (Unless, the alternate requires additional functions for similar purpose to be defined and using memory unnecessarily.)

Share this post


Link to post
Share on other sites

Just realized this is completely unnecessary     :banghead:  

 

_aData = missionNamespace getVariable "SomeArray";

 

_aData is now a pointer to the global array "SomeArray" and will always reflect changes made to it by other scripts, and not a copy unique within the script.  No need to interrupt the engine.  I can just set the element from directly within the script and never have to worry about overwriting anything I don't want to.

 

This is NOT true if "SomeArray" is not an array (lets call it "SomeVariable from here on out).  "SomeVariable" must be an array or any local variable that is declared = to it will be a local copy, and changes made to the local variable will not be reflected in the global variable. 

 

example:

missionNamespace setVariable ["MyGlobalArray",[1,2,3]];

null = [] spawn {
  _myLocalArrayPointer1 = missionNamespace getVariable "MyGlobalArray";
  waitUntil {
     hintSilent str _myLocalArrayPointer1;
     false
  };
};

null = [] spawn {
  _myLocalArrayPointer2 = missionNamespace getVariable "MyGlobalArray";
  sleep 10;
  _myLocalArrayPointer2 set [3,"mind = blown"];
};

//will hint [1,2,3] for 10 seconds, and then continue to hint [1,2,3,"mind = blown"] afterwards

Share this post


Link to post
Share on other sites

that waitUntil should4 never end because the condition returns false always.
also i think that this waitUntil is not needed.

 

ok with a second read i understood what u r doing and it should work...

Edited by sarogahtyp

Share this post


Link to post
Share on other sites

that waitUntil should4 never end because the condition returns false always.

also i think that this waitUntil is not needed.

 

ok with a second read i understood what u r doing and it should work...

 

My understanding is that waitUntil executes code on each frame.  The code I posted as a test is obviously not optimal.  I use waitUntil all the time, and usually with a "sleep" to try to cut down on load on the scheduled queue.  In this example, waitUntil always returns false and therefore loops indefinitely on each frame.  This is not important in regards to what I am pointing out.  

 

What I am trying to illustrate is that:

 

missionNamespace setVariable ["myVariable",1];

 

and then _myVar = missionNamespace getVariable "myVariable";

 

results in a COPY of the variable which is local to the scope that _myVar is defined in.

 

 

 

However, missionNamespace setVariable ["myVariable",[1,2,3]];

 

and then _myVar = missionNamespace getVariable "myVariable";

 

results in a POINTER that references the array rather than makes a copy of it.  

 

 

 

This means that adjustments to any pointers to "myVariable" are reflected globally and immediately IF AND ONLY IF "myVariable" is an array.  If "myVariable" is a variable of some unitary type with a unitary value, then this is not the case.

Share this post


Link to post
Share on other sites

as I sad I understood that while reading it a second time...
 
what I learned is that I get a pointer to an array if I use getVariable. And in mission namspace I can do this to get a complete copy instead of the pointer:[/size]

missionNamespace setVariable ["MyGlobalArray",[1,2,3]];

_copy_of_array = MyGlobalArray;

but what can I do to copy an array that is stored in an objects namespace?
 
Could I use this or is it just copying the pointer?

_someObject setVariable ["MyGlobalArray",[1,2,3]];

_myLocalArrayPointer1 = _someObject getVariable "MyGlobalArray";
_copy_of_array = _myLocalArrayPointer1;

I m a little bit confused now. I just want to know how I can get a pointer and how I can get a copy of an array...
 
EDIT:
As pointers are essential I opened a new topic to solve this question...

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×