How to clone a dictionary (and its content)? - actionscript-3

I googled some time to find a way to clone a dic, and I found some options, but they didnt work. One was to use a byteArray, it works only if you register all the clases involved and your classes have all their constructor parameters with default values <- not a choice in my case.
Ohter was, a copy but only for the first values, this one:
private function clone(original:Dictionary):Dictionary {
var cloned:Dictionary = new Dictionary();
for(var key:Object in original) {
cloned[key] = original[key];
}
return cloned;
}
But, it doesnt clone a dicionary inside the main dictionary.
Do you know how to make a perfect clone of a given dictionary, even if it has another dictionary inside?
Thanks.

Make a recursive function that checks the elements type, if they are dictionaries themselves keep recursing and copying.
Something like (untested):
private function clone(original:Dictionary):Dictionary {
var cloned:Dictionary = new Dictionary();
for(var key:Object in original) {
if( original[key] is Dictionary)
cloned[key] = clone(original[key]);
else
cloned[key] = original[key];
}
return cloned;
}
Edit :
Seems to work for a simple nested dictionary like this :
var dic:Dictionary = new Dictionary();
dic["test"] = 5;
dic["otherDic"] = new Dictionary();
dic["otherDic"]["foo"] = 1;
dic["otherDic"]["bar"] = "bar";
var copy:Dictionary = clone(dic);
dic["test"] = 0;
dic["otherDic"]["foo"] = 0;
dic["otherDic"]["bar"] = "changed";
//copy doesn't change, copy and dic are different dictionaries

Related

Why does (myMC.myArray = myOtherMC.myOtherArray;) cause any changes to myMC.myArray to also change myOtherMC.myOtherArray?

var myArray:Array = new Array();
var myMC:MovieClip = new MovieClip();
myMC.myArray = myArray;
trace(myMC.myArray[10]); //Output: undefined
var newMC:MovieClip = new MovieClip();
newMC.myOtherArray = myMC.myArray;
newMC.myOtherArray[10] = [];
newMC.myOtherArray[10][0] = 100;
trace(myMC.myArray[10]); //Output: 100
Why does that happen, and is there any way to avoid it?
EDIT:
Found a function that can clone associative arrays here.
Here is the function (from the above link):
function clone(source:Object):*
{
var myBA:ByteArray = new ByteArray();
myBA.writeObject(source);
myBA.position = 0;
return(myBA.readObject());
}
Does making the function return type "*" mean that it can be any type? or is it something specific to objects/arrays?
It's because you have passed a reference of the original array to the new clip. Arrays, as objects, are not primitives and therefore will always be referenced.
If you would like to clone an array, keeping the original intact, use this method:
var b:Array = a.concat();
b will be a new array. a can modified without changing b.

AS3 - Using a For Loop to Update Multiple Points and Their Values in an Array

I'm a bit new with AS3 (but not really with coding) so please forgive my ignorance. I'm creating a small function that will be called by a Main Function to update the position of 52 Pointers that have the x and y position of multiple point objects (empty movie clips). It will also then update two global arrays with those values (one array for the x and one for the y).
The problem is, as there are 52 of them, and they will probably grow in quantity, I'd like to be able to use a FOR function to do it, but I can't seem to be able to figure it out.
I get this error: Access of undefined property _point.
Here is a piece of the code that dream about:
function happyFunc():void
{
var avpointers:int = 52;
var vpointx:Array = new Array();
var vpointy:Array = new Array();
for (aa=0; aa<vpointers; aa++)
{
vpointx[aa] = _point[aa].x;
vpointy[aa] = _point[aa].y;
}
}
And this is the code that I'm stuck with...
function reallySadFunc():void
{
_point1 = localToGlobal(new Point(point1.x,point1.y));
//...
_point52 = localToGlobal(new Point(point52.x,point1.y));
vpointx[0] = _point1.x;
vpointx[1] = _point2.x;
//...
//oh god there are 104 lines of this why do I have to suffer
}
Thank you!
If I read your question correctly, I think this is what you need:
public static const CLIP_COUNT:int = 52;
// ...
private function happyFunc(parentClip:DisplayObjectContainer):void
{
var vpointx:Vector.<Number> = new Vector.<Number>(CLIP_COUNT, true);
var vpointy:Vector.<Number> = new Vector.<Number>(CLIP_COUNT, true);
var clipPoint:Point = new Point ();
var globalPoint:Point;
for (var i:int = 0; i < CLIP_COUNT; i++)
{
var childClip:DisplayObject = parentClip.getChildByName("point" +
(i + 1).toString());
clipPoint.x = childClip.x;
clipPoint.y = childClip.y;
globalPoint = parentClip.localToGlobal(clipPoint);
vpointx[i] = globalPoint.x;
vpointy[i] = globalPoint.y;
}
// do something with vpointx and vpointy - they're local variables
// and will go out of scope unless you declare them as class members
// or somehow return them from this function.
}
This function works by taking the parent display object (the one that contains the 52 movie clips - this could be the Stage) and iterates through them by getting each movie clip by name. The code assumes that your movie clips are called point1, point2, ..., point52.
To speed up the local-to-global coordinate conversion, two Point objects are created and then reused during the for loop to avoid creating many temporary Point instances.
Instead of using Array, use Vector.<Number> - this class has better performance than Array does.

as3 operator= /(object assign )

I know that AS3 does not have pointer or reference. Every object is pass by reference already. (I supposed?)
What should I do if I want to do with pointer?
e.g. all object point to one target, I only need to change target's value, then all other object will access different value.
You can effectively get the same behavior by using a helper object to simulate a pointer -- in other words using it to carry the target reference. For instance:
public class PseudoPointer
{
private var obj:Object;
private var prop:String;
public function PseudoPointer(obj:Object, prop:String)
{
// Point to property with name 'prop' on object 'obj'.
this.obj = obj;
this.prop = prop;
}
public function get value():* {
return obj[prop];
}
public function set value(value:*):void {
obj[prop] = value;
}
}
Then you could do this -- assume there's a magicNumber property on an object named target:
var numberPtr = new PseudoPointer(target, "magicNumber");
myDynamicObjectA.numberPtr = numberPtr;
myDynamicObjectB.numberPtr = numberPtr;
myDynamicObjectC.numberPtr = numberPtr;
Now any object that has a reference to the pseudo-pointer can read and write the target property:
numberPtr.value = 42;
You could create a function and in which you give it the value and then subsequently assign it to all of those other variables.
Something like below:
var objectA:Number;
var objectB:Number;
...
function myFunction(newValue:Number):void
{
objectA = newValue;
objectB = newValue;
...
}
You could try setting a variable reference to a function. Then if you update that reference, it would return a different function.
var myFunc:Function;
function funcOne():int {
return 1;
}
function funcTwo():int {
return 2;
}
function getFunc():Function {
return myFunc;
}
myFunc = funcOne;
var myObjOne:Object = new Object();
myObjOne.objFunc = getFunc;
var myObjTwo:Object = new Object();
myObjTwo.objFunc = getFunc;
trace(myObjOne.objFunc.call().call()); // 1
trace(myObjTwo.objFunc.call().call()); // 1
myFunc = funcTwo;
trace(myObjOne.objFunc.call().call()); // 2
trace(myObjTwo.objFunc.call().call()); // 2
This allows any object to point at a single function and have what that returns be updateable for all of them simultaneously. It's not the prettiest code and it's not as type-safe as if ActionScript had delegates to enforce the signatures of what's set for myFunc, but it is still a pretty flexible model if you get creative with it.
Have those pointers point to something that contains the object or has the object as a property on it.
So
var myArr:Array = [new YourObject()];
var client1:ArrayClient = new ArrayClient();
client1.array = myArr;
var client2:ArrayClient = new ArrayClient();
client2.array = myArr;
myArr[0] = new YourObject();
//client1.array[0]==client2.array[0]==the new object
However, this seems to be a great way to get yourself into a lot of trouble really quickly. What's the use case?

How can I copy an ArrayCollection of transfer objects in actionscript?

public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
data.addItem(originalData[index]);
}
return data;
}
originalData is the original state of my data from database.
data is a copy of originalData used to be manipulated as the provider for my List component.
There's a button I use to call the create() function above, that
would mean, I want to revert all changes in data, and go back to
everything I have in originalData.
But when I debug my function, originalData has all the changes made in data.
When I use
list.selectedItem.thing = "new string";
is supposed to modify data[index].thing, because data is my List.dataprovider. but it changes originalData[index].thing also and this collection wasn't used for anything, except for creating a copy of itself!
I don't know why this happens. I didn't know how to phrase this behaviour as a google query.
Please, if you don't understand the question, comment it so I can try and make it clearer. This has consumed more time than its functionality is worth.
EDIT:
I've also tried this, but it doesn't work:
public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
// initializing a Item object.
var dataItem:Item = new Item();
dataItem = originalData[index] as Item;
data.addItem(dataItem);
}
return data;
}
EDIT 2:
Based on your answers and some research I came up with this generic function to copy arrayCollections made of custom objects.
public static function copy(objectClassName:String, objectClass:Class, arrayCollection:ArrayCollection):ArrayCollection{
var index:int = 0;
var length:int = arrayCollection.length;
var copy:ArrayCollection = new ArrayCollection();
for(index; index < length; index++){
registerClassAlias(objectClassName,objectClass);
copy.addItemAt(ObjectUtil.copy(arrayCollection.getItemAt(index)) as objectClass,index);
}
return copy;
}
As Tom says, this is because AS3 passes by reference. If you don't want to modify the original values then you should, again as Tom says, create copies of them.
Fortunately, AS3 has a utility to do this -- ObjectUtils.copy. Try this code instead of your original:
public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
data.addItem(mx.utils.ObjectUtil.copy(originalData[index]));
}
return data;
}
Do take note that copy() returns a generic Object. If you want to access any of its properties in a type-safe manner you'll have to cast it to its type.
A bit more about the pass-by-reference deal. Let's say we have items a, b, and c floating around in memory. You put them into an array(originalData). originalData now contains references to a, b, and c. You then create an ArrayCollection and insert (again) references to a, b, c. Objects a, b, and c aren't what's being stored in either the array or the ArrayCollection. So when you update originalData[0] you're getting a reference to an object (a), and updating it.
Likewise when you update ArrayCollection.getItemAt(0) you're getting the same reference and updating the underlying object which is why you're getting the behavior you're getting. Making a copy and inserting it means you're referencing an entirely new object.
I am not used to actionscript, but it looks to me like you are putting references to objects from one arrayCollection to another arrayCollection. So if you change those objects, this will be reflected in both arrayCollections since they refer to the same objects.
To avoid this you should make copies from the original objects to put in the new arrayCollection.
To create a deep copy of an array and keep the type, use the following method:
public static function clone(source:Object):*
{
var myBA:ByteArray = new ByteArray();
myBA.writeObject(source);
myBA.position = 0;
return(myBA.readObject());
}
This is the way proposed by Adobe.
Update: idea for a type safe copy function
public static function copyTypeSafe( ac:ArrayCollection ):ArrayCollection
{
var cloneAc:ArrayCollection = new ArrayCollection();
if( ac.length == 0 ) {
return cloneAc;
}
var className:String = getQualifiedClassName( ac.getItemAt(0) );
registerClassAlias( className, (getDefinitionByName( className ) as Class ) );
for each (var obj:Object in ac)
{
cloneAc.addItem( ObjectUtil.copy( obj ) );
}
return cloneAc;
}
You need to do a registerClassAlias call for each of the classes inside of your collection, then just use the function clone with the ByteArray suggested above and it will work fine. The key is to do a registerClassAlias call for each type inside of the ArrayCollection which you want to maintain. In my case:
var productGroupClassName:String = getQualifiedClassName(ProductGroup);
registerClassAlias( productGroupClassName, ProductGroup );
var productClassName:String = getQualifiedClassName(Product);
registerClassAlias( productClassName, Product );
//Need to create a copy so the original values will not be altered
dataProvider = copyTypeSafe(EnalityData.productsInfo);

Add multiple movieclips, not replacing the old ones

So, in short, my problem is this. I am using a variable which is a movieclip loaded from an external swf. I want to "spawn" multiple instances of the movieclip that all react to the same code, so for example if I say var1.x = 100, they all are at 100x. But my problem is when I run addChild(var1) multiple times(I'm not actually typing in addChild(var1) over and over, I just have it set to add them at random times), the new child just replaces the old one, instead of making multiple movieclips. Should I do something like
var var1:MovieClip
var var2:MovieClip = new var1 ?(which doesnt work for me btw, gives me errors)
Oh, heres the code, and also, I am pretty new to as3 fyi, still don't even know how arrays work, which was my second guess to the problem.
var zombieExt:MovieClip;
var ldr2:Loader = new Loader();
ldr2.contentLoaderInfo.addEventListener(Event.COMPLETE, swfLoaded2);
ldr2.load(new URLRequest("ZombieSource.swf"));
function swfLoaded2(event:Event):void
{
zombieExt = MovieClip(ldr2.contentLoaderInfo.content);
ldr2.contentLoaderInfo.removeEventListener(Event.COMPLETE, swfLoaded2);
//zombieExt.addEventListener(Event.ENTER_FRAME, moveZombie)
zombieExt.addEventListener(Event.ENTER_FRAME,rotate2);
function rotate2 (event:Event)
{
var the2X:int = playerExt.x - zombieExt.x;
var the2Y:int = (playerExt.y - zombieExt.y) * 1;
var angle = Math.atan(the2Y/the2X)/(Math.PI/180);
if (the2X<0) {
angle += 180;
}
if (the2X>=0 && the2Y<0) {
angle += 360;
}
//angletext.text = angle;
zombieExt.rotation = (angle*1) + 90;
}
playerExt.addEventListener(Event.ENTER_FRAME,spawn1);
function spawn1 (event:Event)
{
if(playerExt.y < 417)
{
var someNum:Number = Math.round(Math.random()*20);
if(someNum == 20)
{
addChild(zombieExt)
zombieExt.x = Math.round(Math.random()*100)
zombieExt.y = Math.round(Math.random()*100)
}
}
}
}
addChild() does not create new instances. It is used to add an already created instance to the display list. If you call addChild() multiple times on the same instance then you are just readding itself.
Also each instance is unique, you can not globally change the x position of an instance by changing another one of them. What you would do is as Henry suggests and add each new instance of a MovieClip into an array, then whenever you change something you can loop through the array and apply the changes to each instance.
You can not go var2:MovieClip = new var1 either since var1 is an instance and not a class.
Here's a different method of receiving loaded MovieClips, which i use when i need many copies of the item.
in the swf you are loading, give the target movieclip a linkage name in the library, for this example i will use "foo"
private var loadedSwfClass:Class
private var newZombie:MovieClip;
private var zombieArray:Array = new Array();
function swfLoaded2(event:Event):void
{
loadedSwfClass = event.target.applicationDomain.getDefinition("foo");
for(var n:int = 0; n<100; n++){
newZombie = new loadedSwfClass()
zombieArray.push(newZombie);
addChild(newZombie);
}
}
as per this tutorial
http://darylteo.com/blog/2007/11/16/abstracting-assets-from-actionscript-in-as30-asset-libraries/
although the comments say that
var dClip:MovieClip = this;
var new_mc = new dClip.constructor();
this.addChild(new_mc);
will also work.
It sounds like you might be accessing the same instance some how in your code. It would be helpful to see your code to figure this one out.
If I wanted to load in one swf files and add a MovieClip multiple times I would place it in the library of that SWF file. And then instantiate it and store it into an object pool or a hash or some list.
// after the library were finished loading
var list:Array = [];
for(var i:int=0; i<10; i++) {
var myCreation:MySpecialThing = new MySpecialThing();
addChild(myCreation);
list.push(myCreation);
}
where my library would contain a linkage to the class MySpecialThing.
Calling addChild(var1) multiple times on the same parent doesn't have any effect (unless you have added another child to the same parent in between, in which case it will change the child index and bring var1 to the top). If you call it on different parents, it will just change the parent of var1, doesn't duplicate. Call addChild(new MovieClassName()) at random times instead to add new copies of it. Use an array as suggested here to access them later.
Wow, thanks there henry, just using an array did exactly what I needed, and made things alot simpler.
when you load in using a loader you only get 1 instance, however you can do some funky reflection to determine what class type the given loader.content is, and then instantiate them using that. For Example:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loader_completeHandler);
loader.load(new URLRequest("ZombieSource.swf"));
var classType:Class;
function loader_completeHandler(evt:Event):void
{
var loadInfo:LoaderInfo = (evt.target as LoaderInfo);
var loadedInstance:DisplayObject = loadInfo.content;
// getQualifiedClassName() is a top-level function, like trace()
var nameStr:String = getQualifiedClassName(loadedInstance);
if( loadInfo.applicationDomain.hasDefinition(nameStr) )
{
classType = loadInfo.applicationDomain.getDefinition(nameStr) as Class;
init();
}
else
{
//could not extract the class
}
}
function init():void
{
// to make a new instance of the ZombieMovie object, you create it
// directly from the classType variable
var i:int = 0;
while(i < 10)
{
var newZombie:DisplayObject = new classType();
// your code here
newZombie.x = stage.stageWidth * Math.random();
newZombie.x = stage.stageHeight * Math.random();
i++;
}
}
Any problems let me know, hope this helps.