How to share an Array between all Classes in an application? - arrays
I want to share an Array which all classes can "get" and "change" data inside that array. Something like a Global array or Multi Access array. How this is possible with ActionScript 3.0 ?
There are a couple of ways to solve this. One is to use a global variable (as suggested in unkiwii's answer) but that's not a very common approach in ActionScript. More common approaches are:
Class variable (static variable)
Create a class called DataModel or similar, and define an array variable on that class as static:
public class DataModel {
public static var myArray : Array = [];
}
You can then access this from any part in your application using DataModel.myArray. This is rarely a great solution because (like global variables) there is no way for one part of your application to know when the content of the array is modified by another part of the application. This means that even if your data entry GUI adds an object to the array, your data list GUI will not know to show the new data, unless you implement some other way of telling it to redraw.
Singleton wrapping array
Another way is to create a class called ArraySingleton, which wraps the actual array and provides access methods to it, and an instance of which can be accessed using the very common singleton pattern of keeping the single instance in a static variable.
public class ArraySingleton {
private var _array : Array;
private static var _instance : ArraySingleton;
public static function get INSTANCE() : ArraySingleton {
if (!_instance)
_instance = new ArraySingleton();
return _instance;
}
public function ArraySingleton() {
_array = [];
}
public function get length() : uint {
return _array.length;
}
public function push(object : *) : void {
_array.push(object);
}
public function itemAt(idx : uint) : * {
return _array[idx];
}
}
This class wraps an array, and a single instance can be accessed through ArraySingleton.INSTANCE. This means that you can do:
var arr : ArraySingleton = ArraySingleton.INSTANCE;
arr.push('a');
arr.push('b');
trace(arr.length); // traces '2'
trace(arr.itemAt(0)); // trace 'a'
The great benefit of this is that you can dispatch events when items are added or when the array is modified in any other way, so that all parts of your application can be notified of such changes. You will likely want to expand on the example above by implementing more array-like interfaces, like pop(), shift(), unshift() et c.
Dependency injection
A common pattern in large-scale application development is called dependency injection, and basically means that by marking your class in some way (AS3 meta-data is often used) you can signal that the framework should "inject" a reference into that class. That way, the class doesn't need to care about where the reference is coming from, but the framework will make sure that it's there.
A very popular DI framework for AS3 is Robotlegs.
NOTE: I discourage the use of Global Variables!
But here is your answer
You can go to your default package and create a file with the same name of your global variable and set the global variable public:
//File: GlobalArray.as
package {
public var GlobalArray:Array = [];
}
And that's it! You have a global variable. You can acces from your code (from anywhere) like this:
function DoSomething() {
GlobalArray.push(new Object());
GlobalArray.pop();
for each (var object:* in GlobalArray) {
//...
}
}
As this question was linked recently I would add something also. I was proposed to use singleton ages ago and resigned on using it as soon as I realized how namespaces and references work and that having everything based on global variables is bad idea.
Aternative
Note this is just a showcase and I do not advice you to use such approach all over the place.
As for alternative to singleton you could have:
public class Global {
public static const myArray:Alternative = new Alternative();
}
and use it almost like singleton:
var ga:Alternative = Global.myArray;
ga.e.addEventListener(GDataEvent.NEW_DATA, onNewData);
ga.e.addEventListener(GDataEvent.DATA_CHANGE, onDataChange);
ga.push(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten");
trace(ga[5]); // 5
And your Alternative.as would look similar to singleton one:
package adnss.projects.tchqs
{
import flash.utils.Proxy;
import flash.utils.flash_proxy;
public class Alternative extends Proxy
{
private var _data:Array = [];
private var _events:AltEventDisp = new AltEventDisp();
private var _dispatching:Boolean = false;
public var blockCircularChange:Boolean = true;
public function Alternative() {}
override flash_proxy function getProperty(id:*):* {var i:int = id;
return _data[i += (i < 0) ? _data.length : 0];
//return _data[id]; //version without anal item access - var i:int could be removed.
}
override flash_proxy function setProperty(id:*, value:*):void { var i:int = id;
if (_dispatching) { throw new Error("You cannot set data while DATA_CHANGE event is dipatching"); return; }
i += (i < 0) ? _data.length : 0;
if (i > 9 ) { throw new Error ("You can override only first 10 items without using push."); return;}
_data[i] = value;
if (blockCircularChange) _dispatching = true;
_events.dispatchEvent(new GDataEvent(GDataEvent.DATA_CHANGE, i));
_dispatching = false;
}
public function push(...rest) {
var c:uint = -_data.length + _data.push.apply(null, rest);
_events.dispatchEvent(new GDataEvent(GDataEvent.NEW_DATA, _data.length - c, c));
}
public function get length():uint { return _data.length; }
public function get e():AltEventDisp { return _events; }
public function toString():String { return String(_data); }
}
}
import flash.events.EventDispatcher;
/**
* Dispatched after data at existing index is replaced.
* #eventType adnss.projects.tchqs.GDataEvent
*/
[Event(name = "dataChange", type = "adnss.projects.tchqs.GDataEvent")]
/**
* Dispatched after new data is pushed intwo array.
* #eventType adnss.projects.tchqs.GDataEvent
*/
[Event(name = "newData", type = "adnss.projects.tchqs.GDataEvent")]
class AltEventDisp extends EventDispatcher { }
The only difference form Singleton is that you can actually have multiple instances of this class so you can reuse it like this:
public class Global {
public static const myArray:Alternative = new Alternative();
public static const myArray2:Alternative = new Alternative();
}
to have two separated global arrays or even us it as instance variable at the same time.
Note
Wrapping array like this an using methods like myArray.get(x) or myArray[x] is obviously slower than accessing raw array (see all additional steps we are taking at setProperty).
public static const staticArray:Array = [1,2,3];
On the other hand you don't have any control over this. And the content of the array can be changed form anywhere.
Caution about events
I would have to add that if you want to involve events in accessing data that way you should be careful. As with every sharp blade it's easy to get cut.
For example consider what happens when you do this this:
private function onDataChange(e:GDataEvent):void {
trace("dataChanged at:", e.id, "to", Global.myArray[e.id]);
Global.myArray[e.id]++;
trace("new onDataChange is called before function exits");
}
The function is called after data in array was changed and inside that function you changing the data again. Basically it's similar to doing something like this:
function f(x:Number) {
f(++x);
}
You can see what happens in such case if you toggle myArray.blockCircularChange. Sometimes you would intentionally want to have such recursion but it is likely that you will do it "by accident". Unfortunately flash will suddenly stop such events dispatching without even telling you why and this could be confusing.
Download full example here
Why using global variables is bad in most scenarios?
I guess there is many info about that all over the internet but to be complete I will add simple example.
Consider you have in your app some view where you display some text, or graphics, or most likely game content. Say you have chess game. Mayby you have separated logic and graphics in two classes but you want both to operate on the same pawns. So you create your Global.pawns variable and use that in both Grahpics and Logic class.
Everything is randy-dandy and works flawlessly. Now You come with the great idea - add option for user to play two matches at once or even more. All you have to do is to create another instance of your match... right?
Well you are doomed at this point because, every single instance of your class will use the same Global.pawns array. You not only have this variable global but also you have limited yourself to use only single instance of each class that use this variable :/
So before you use any global variables, just think twice if the thing you want to store in it is really global and universal across your entire app.
Related
(Unity) Why is my list of a class not getting saved in JSON
I want to save a list of dialogue items. I made a script called dialogue item which is just one message, I made an array of that so it can be a conversation, and I made a list of that conversation so that I can have multiple conversations. [SerializeField] public List<DialogueList> dialogue = new List<DialogueList(); So I made this variable of a class named DialogueList, that contains the dialogueitems. [System.Serializable] public class DialogueList { [SerializeField] public string convoName; [SerializeField] public int dialogueID; [SerializeField] public DialogueItem[] dialogues; } I want to save these conversations so that I can load them in specific languages in later stages. But for some reason my JSON file is empty when I try to save my variable named dialogue. string jsonData = JsonUtility.ToJson(dialogue, true); string _fullPath = "/caroline-dialogue" + ".json"; File.WriteAllText(Application.persistentDataPath + _fullPath, jsonData); I tried using a different method, that actually works, but it can get really messy. for (int i = 0; i < dialogue.Count; i++) { string jsonData = JsonUtility.ToJson(dialogue[i], true); string _fullPath = "/caroline-dialogue-" + i + ".json"; File.WriteAllText(Application.persistentDataPath + _fullPath, jsonData); } My question is: What am I doing wrong? Am I forgetting something? My guess it that something is wrong with the variable named dialogue, since saving all the dialogueitems with a for loop works. I hope I explained my problem well enough.
Serialization/deserialization of the arrays or lists is not supported using the JsonUtility. You would need to wrap the list in a serializable class: [Serializable] private class DialogueListWrapper { public List<DialogueList> objects; }
Rest parameter problems
I'm attempting to have a class inherit a rest parameter from a parent class. Unfortunately for some reason the inherited rest parameter's inputs are treated as one whole index rather than each one acting like its own index. Here's the code for the parent class with a rest parameter that works properly: package { import flash.events.MouseEvent; import flash.display.Stage; public class SubClassText extends ClassText { protected var parentText:ClassText; protected var setStatValues; protected var className:String; protected var classType:int; public function SubClassText(textKey:String, textName:String, stageInstance:Stage, isVisible:Boolean, alignToObject:Object, direction:String, xDistance:Number, yDistance:Number, onOverText:String, parentText:ClassText, className:String, classType:int, ... setStatValues) { super(textKey, textName, stageInstance, isVisible, alignToObject, direction, xDistance, yDistance, onOverText); this.parentText = parentText; this.parentText.subClassArray.push(this); this.setStatValues = setStatValues; this.className = className; this.classType = classType; } //Called when the player clicks this object override protected function onClick(e:MouseEvent) { trace(this.setStatValues.length); Entity.getEntity("entityName", "player").setStat("className", this.className); Entity.getEntity("entityName", "player").setStat("classType", this.classType); for(var i:int = 0; i < this.setStatValues.length; i++) { Entity.getEntity("entityName", "player").setStat(Main.statArray[i], this.setStatValues[i]); } Main.setClassVisibility(this.parentText.subClassArray, true); } //Adds this object to it's parent's class array override protected function addToArray() { Main.subClasses.push(this); } } } I have the trace() call in the onClick listener to see the length of the rest parameter. In all instances of this class the rest parameter returns the proper amount of inputs (in this case, 5 since that's how many stats an entity has). And here's the class that extends this parent class: package { import flash.events.MouseEvent; import flash.display.Stage; public class AllySubClassText extends SubClassText { private var displayName:String; public function AllySubClassText(textKey:String, textName:String, stageInstance:Stage, isVisible:Boolean, alignToObject:Object, direction:String, xDistance:Number, yDistance:Number, onOverText:String, parentText:ClassText, displayName:String, className:String, classType:int, ... setStatValues) { super(textKey, textName, stageInstance, isVisible, alignToObject, direction, xDistance, yDistance, onOverText, parentText, className, classType, setStatValues); this.displayName = displayName; } //Called when the player clicks this object override protected function onClick(e:MouseEvent) { trace(this.setStatValues.length); Main.tryAddAlly(this.displayName, this.className, this.classType, this.setStatValues); Main.setAllyVisibility(this.parentText.subClassArray, true); } //Adds this object to it's parent's class array override protected function addToArray() { Main.allySubClasses.push(this); } } } As you can see, I have the same rest parameter and the same trace() call. For a better understanding here's an example piece of an instance of SubClassText: var beserker:SubClassText = new SubClassText("beserker", "Beserker", stage, false, warrior, "downCenter", 0, 0, "Beserkers are crazy strong fighters", warrior, "Beserker", 0, 15, 5, 10, 10, 10); Then I can go into that SubClassText and trace its rest parameter length (setStatValues, I'm going to trace it from the fla document so for now I'm going to make that parameter public instead of protected): trace(beserker.setStatValues.length); //5 Now here's an example of an instance of AllySubClassText: var knight:AllySubClassText = new AllySubClassText("knight", "War: The Knight", stage, false, warriors, "downCenter", 0, 0, "War is a strong Knight", warriors, "War", "Knight", 0, 15, 5, 10, 10, 10); Then when I trace the knight instance's rest parameter length: trace(knight.setStatValues.length); //1 The example pieces were taken directly from my code, I just shortened the onHover string so it's easier to read. Sorry for the huge amount of inputs on the classes, UI code quickly took a lot of parameters to work correctly. I'm not sure why all inputs are being treated as one index rather than individual indexes. I even know it's doing this because here's what happens when I trace just the rest parameter: trace(knight.setStatValues + ", " + knight.setStatValues.length); //15,5,10,10,10, 1 Any help would be greatly appreciated. It could be a minor oversight, but I'm stumped right now.
The problem is that ...rest parameters are an array. That means when you pass that array to yet another function as a ...rest parameter, it will be of length 1, because you only passed one parameter: the single array. It doesn't automatically "unwrap" an array into its elements. Try this: function first(...firstRest):void { trace(firstRest.length); second(firstRest); } function second(...secondRest):void { trace(secondRest.length); } first(1,2,3); I wouldn't recommend using ...rest anyway. Create a class that holds all the status variables and pass an object of that class to the constructor. That allows you to do boundary checks on the values, dispatch change events to update views displaying the stats, etc. These constructor calls you show there look quite bloated. Passing a configuration object will help in reducing the mess.
Need to verify size of an array in unit testing [duplicate]
This question already has answers here: How do I test a class that has private methods, fields or inner classes? (58 answers) Closed 8 years ago. Class Elem{ private ArrayList<someType> arr = new ArrayList<>(); public void addElement(someType var) { arr.add(var); } public someType bestelement() { someType first= arr.get(0); arr.remove(0); return first; } } I have written test case for this method and it's running successfully but I need to know how can I be sure that the remove() method was called and the size of array list was reduced by 1? Please do tell me how to get the size of arr in my test case? Test case for this method Class ElemTest{ private Elem obj = new Elem(); private someType var1; private someType var2; private ArrayList<someType> testArr = new ArrayList<>(); #Test public void bestElementTest() { obj.addElement(var1); obj.addElement(var2); testArr.add(var1); testArr.add(var2); someType result = testArr.get(0); assertEquals("test failed", result, obj.bestElem()); } }
Given your example its difficult to advise you, but in general terms you options are to set the class into a known state, call you method which chnages the class and then check that the state has changed in the way you expect. In this specific case if you have access to the internal list somewhere then you can check that the list has changed (ie the element has been removed). If you don't have access to the internal list then you need to check other things, like: if you call bestElement() again when the object contains more than 1 element you get a different element if you call bestElement() again when the object should not have any more elements you get an exception These obviously rely on being able to set the object into one of the above states and we don't know if that is possible from the sample. My final piece of advise would be to focus the test on the behaviour that you want to see from your class, not in the implementation of how the class works.
Giving each object that share the same class, their own individual variables that won't affect the other variables
Good Evening/ Morning. This is a games related question. I am facing an issue where I have three objects, (three goblins) data typed to the same class. These three objects are in an array and data typed to that class, and initialized as the array. private var goblin1:Goblin = new Goblin(); private var goblin2:Goblin = new Goblin(); private var goblin3:Goblin = new Goblin(); So the variables above have been then placed into an array. private var aGoblinArray = new Array(container.goblin1, container.goblin2, container.goblin3); After placing the objects into an array I have looped through all of my goblins. for (var i:int = 0; i < aGoblinArray.length; i++) { var goblin:Goblin = aGoblinArray[i]; } now I have a hitTest in the for loop and the hitTest is: if (goblin.hitTestPoint(_character.x + 100, _character.y - 45, true)) { goblinCanMove = false; trace("lance hits"); //hitOnce if (!hitOnce) { hitOnce = true; trace("take on damage"); } goblin.moveBack(); goblin.minusHealth(); } This means if this player hits any of the goblins, they will do this function. How ever in the goblin class. public static var goblinLife; int; goblinLife = 2;//put in main constructor public function minusHealth():void { goblinLife --; checkDeath(); } private function checkDeath():void { if (goblinLife == 0) { parent.removeChild(this); } } the problem is, if I hit goblin1, then goblinLife would = 1. This means all othet goblins(goblin2 and 3) will have 1 life. Since they share the same class. if goblin1 dies, he is removed and the var goblinLife would = 0; Now I can reset it back to 2, but this will fix half of the problem. My question is, is there a way on how I can make sure each goblin has it's own individual life system. Thank you in advance.
Thank you very much Pan and Marty! Static meant that it could be changed from any class and any function. Private means that it will be protected to each individual goblin. From changing public static var goblinLife; int; to private var goblinLife; int; it means that each individual goblin will have their own variable, that no other class or object of the same class can change. Thank you Pan and Martyn. I guess I need to read AS3 101: Quick Tip – When to Use Static Properties and Methods and other coding books!
Actionscript 3 eventlisteners, hittestobject and arrays of custom objects
Another desperately stuck first year student here. And I have to use FlashCS as my coding environment. And it sucks. So I'll try some well constructed and clear questions. There is: public var object: symbol1; public var objectarray: Array = new Array(); in my main. Then a function there that uses a timer and spawns a new object and pushes it onto the array: object = new symbol1; objectarray.push(object) but then when I trace() the .length of the array it displays TWO numbers of the array length every timer period in the output. As in: 1 1 2 2 3 3 etc. This is my first mystery. Why two not one? because there is no way I'm calling the function that includes the trace () twice. Also I think I need to be able to remove my object from the objectarray when it goes off the stage, but the objectarray.pop() doesn't seem to work if I use it like so in a function: if (object.y == stage.stageHeight) objectarray.pop() As in I try trace() the array.length before and after the .pop(), but it just keeps going up by one every timer period. And the other, bigger issue is I want to know if you are allowed to put the .addEventListeners that you usually place right under the main function of any class into a statement loop. As in I've got class extends Main { class function() { for (var i:Number = 0; i < objectarray.length; i++){ objectarray[i].addEventListener(Event.ENTER_FRAME, collision);}} And then, if it is allowed, the program doesn't seem to enter the collision function of this same class anyway. function collision (event:Event) : void{ if (this.hitTestObject(object)){ trace('hit');}} so I searched and ended up adding a var clip:MovieClip = MovieClip(e.target); in the first line of the function, but then it didn't work and I realized I on't understand what's it meant to do, what's going on anymore and what is the syntax for this casting. Thank you very much. Edit/Update: adding more of my code eventhough I hate copypasting it like this. This is the symbol class that is going to change when an object of another class hits it public class Head extends Main { public function Head(){ this.stop(); for (var i:Number = 0; i < nicesnowflakearray.length; i++){ nicesnowflakearray[i].addEventListener(Event.ENTER_FRAME, snowhit); } } public function snowhit(event:Event) : void { if (this.hitTestObject(nicesnowflake)){ //I changed this line to (e.currentTarget.hitTestObject(nicesnowflake)) as Atriace suggested, but nothing changed, and I just don't understand why my version wouldn't work. trace("hit"); } } And this is the class that spawns the objects that are supposed to hit the Head object: public class Main extends MovieClip { public var nicesnowflake: fallingsnow; var nicesnowflakespawntimer: Timer = new Timer(1000); public var nicesnowflakearray: Array = new Array(); public function Main() { nicesnowflakespawntimer.addEventListener(TimerEvent.TIMER, nicesnowflakespawn); nicesnowflakespawntimer.start(); } public function nicesnowflakespawn(event:TimerEvent) : void { nicesnowflake = new fallingsnow; nicesnowflake.x = Math.random()* stage.stageWidth; nicesnowflake.y = - stage.stageHeight + 100; nicesnowflakearray.push(nicesnowflake); stage.addChild(nicesnowflake); trace(nicesnowflakearray.length); }
Why two, not one? Anytime you extend another class, it implicitly calls the parent class' constructor. We know this as super(), and can be quite handy. This is why you're getting doubles on your trace statement. Technically, you can choose not to call super(). .pop() It should remove the last element from that array, however, I'm thinking that if an arbitrary object leaves the stage, you can't be gauranteed it'll be the last element. Ergo, you want splice() if (object.y == stage.stageHeight) { objectarray.splice(objectarray.indexOf(object), 1) } Event Listeners I didn't follow your quandary, so I'll just try to rewrite what I think you were trying to do. package { import flash.display.MovieClip; public class Main extends MovieClip { private var objectarray:Array = []; // Note, I haven't populated it with anything, I'm assuming you have. private var theWall:MovieClip = new MovieClip(); // I haven't added this to the stage, or given it shape. You need to for hitTestObject to work. public function Main() { // This is your constructor. for (var i:Number = 0; i < objectarray.length; i++) { objectarray[i].addEventListener(Event.ENTER_FRAME, collision); } } private function collision(e:Event):void { if (e.currentTarget.hitTestObject(theWall)) { trace('hit'); } } } } Of note, you may want to look at a guide to hitTestObject() if it's giving you issues.