Shared objects not working correclty in flex -- Mobile - actionscript-3

I have a very simple test going with shared objects in flex with mobile I have a person class.
package
{
import flash.display.MovieClip;
public class Person extends MovieClip
{
var personsname:String="";
public function Person(name:String)
{
personsname = name;
}
}
}
And then some simplish code in a view.
var person1:Person;
var person2:Person;
var person3:Person;
var person4:Person;
var thePeople:Array=[];
var so:SharedObject;
function init():void{
person1 = new Person("james");
person2 = new Person("mike");
person3 = new Person("Amanda");
person4 = new Person("Shelly");
thePeople.push(person1,person2,person3,person4);
//so = SharedObject.getLocal("savedData"); //clear it again
///so.clear(); // clear it again
savePeople();
getPeople();
}
private function savePeople():void{
so = SharedObject.getLocal("savedData");
if(so.data.thePeopleArray == null){
so.data.thePeopleArray = thePeople;
so.flush();
}
}
private function getPeople():void{
so = SharedObject.getLocal("savedData");
var thePeeps:Array = so.data.thePeopleArray;
trace(thePeeps);
}
The first time I run this it traces out
[object Person] 4 times
I close the emulator and rebuild and run it traces out
,,,
If I clear out the so it show the [object Person] again, but comment out get the ,,,
Can shared objects even store an array of objects properly. It is the same with the persistanceManager I believe.

The root of the problem here is that you are trying to save an instance MovieClip into the SharedObject. Since the MovieClip is an intrinsic object (native to flash) it cannot be converted into a form which can be stored. This causes flash to convert the data into a generic Object which is stored to disk. I can only guess at exactly what is going into the SharedObject at this point.
It seems to work the first time because flash does not actually load the shared object in the getPeople() call, it just uses the object which is already in memory. The second time the app runs it reads the generic object from disk and creates a generic object.
There is another problem which is that the flash player does not know to pass data to the constructor when it reads the object.
There are a few possible workarounds, some are:
Store the data as text
Store the data as a ByteArray
Store the data in a "Data Object"
Each of these requires some conversion during the read and write process, but this can be simplified using an interface. This also adds flexibility in case your object changes you will still be able to read the data in the SharedObject.
1: Text
As an example, you might add two methods to the Person object, call them serialise() and deserialise(). The serialise() method will return text which can be stored in the shared object. The deserialise() will parse text and populate the values of the object.
Here's a sample to illustrate this:
class Person {
private var name:String;
private var age:int;
public function serialise():String {
return [name, age].join("\t");
}
public function deserialise(input:String):void {
var tokens:Array = input.split("\t");
name = tokens[0];
age = parseInt(tokens[1]);
}
public static function create(name:String, age:int):Person
{
var output:Person = new Person();
output.name = name;
output.age = age;
return output;
}
}
For ease of use we can create a class for managing a collection of people:
class People {
private var people:Vector.<Person> = new Vector.<Person>();
public function clear():void {
people.splice(0, people.length);
}
public function add(person:Person):void {
people.push(person);
}
public function serialise():String {
var output:Array = [];
for each (var person:Person in people)
output.push(person.serialise());
return output.join("\n");
}
public function deserialise(input:String):void {
var tokens:Array = input.split("\n");
for each (var token:String in tokens) {
var person:Person = new Person();
person.deserialise(token);
add(person);
}
}
public function save():void {
var so:SharedObject = SharedObject.getLocal("cookie");
so.data.people = serialise();
so.flush();
}
public function load():void
{
var so:SharedObject = SharedObject.getLocal("cookie");
if (so.data.people != null)
deserialise(so.data.people);
}
}
Usage:
var people:People = new People();
people.load();
trace(people.serialise());
people.clear();
people.add(Person.create("Candy", 21));
people.add(Person.create("Sandy", 23));
people.add(Person.create("Randy", 27));
people.save();
trace(people.serialise());
An obvious flaw in this example is that the \n and \t characters cannot be used as part of the data (ie for the name of a person). This is a common short-coming with text data.
** Update: Look into the built-in JSON methods for a consistent approach to serialising objects to and from text.
2: ByteArray
Very similar to the text method described above, except the serialise/deserialise methods would accept an additional parameter of a ByteArray, which the object would write to. The ByteArray would then be saved and loaded from the shared object. The advantages of this method is that resulting data is usually is compact and versatile than the text method.
Flash also defines the IDataInput and IDataOutput interface which can be used here.
3: Data Objects
If you still prefer the storing objects directly, then you could create a proxy object which serves the sole purpose of carrying data. A data object (aka DO) is a an object which only has variables, and not methods. Eg:
class PersonDO {
public var name:String;
}
It would be used something like this:
var person2:Person;
var person3:Person;
var person4:Person;
var thePeople:Array=[];
var so:SharedObject;
function init():void{
person1 = new Person("james");
person2 = new Person("mike");
// store the people data into data objects
person1DO = new PersonDO();
person1DO.name = person1.name;
person2DO = new PersonDO();
person2DO.name = person2.name;
thePeople.push(person1DO,person2DO);
savePeople();
// load the people into data objects
getPeople();
person1 = new Person(thePeople[0].name);
person2 = new Person(thePeople[1].name);
private function savePeople():void{
so = SharedObject.getLocal("savedData");
if(so.data.thePeopleArray == null){
so.data.thePeopleArray = thePeople;
so.flush();
}
}
private function getPeople():void{
so = SharedObject.getLocal("savedData");
var thePeeps:Array = so.data.thePeopleArray;
trace(thePeeps);
}
Even though this may appear simpler than the alternatives there are downsides to storing objects directly:
- Stored data is very fragile - if you change the object then your data will become unusable unless you have several versions of each object.
- You need to ensure that a reference to the data object is compiled into the application.
- A common usage scenario for Shared Objects is to save data objects from one SWF, and load them in another. You need ensure that both SWFs use identical version of the class being saved and loaded.
Hope that helps.

Related

Saving objects with writeObject: What objects can be saved using this method?

I'm working making my first foray into the exciting world of byteArrays!
End Goal
I want to save the positions and other properties of each game element (in this case, blocks in a breakout clone) as part of a level design feature for my app, and also to more easily design levels for the game.
Current Approach
Convert data from a Vector of custom class instances (the bricks) into a ByteArray and save that data to a text file. It seems like this is working fine up to this point (can't be sure until I successfully extract that data back into a Vector object, because the text file that gets saved is pure gobbledygook).
I load a level design by reading the text file into a byteArray and then doing writeObject() into a Vector with the intention of now having a vector that contains all the bricks (this is not working).
The Problem
When I try to run my load function, the file loads, and the byteArray gets "filled" with data, but when I try to do writeObject, I get all these errors(one copy of the following errors for each brick in the vector).
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92cdcb9 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92cde09 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df041 to flash.geom.ColorTransform.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df161 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df281 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df431 to flash.media.SoundTransform.
TypeError: Error #2004: One of the parameters is invalid.
My custom brick class is an extension of the Sprite class. But it additionally has properties that depend on Point and ColorTransform objects. Oddly, nowhere in my custom class do I have any reference to or use of SoundTransform... so that error seems glaringly odd. I'll post my custom class if anyone wants to look at it.
My Save and Load Methods
private function saveLevelDesign(brVec:Vector.<LineTestBlock>):void{
trace("save");
var file:File = File.documentsDirectory;
file = file.resolvePath("AnimationFiles/brickLevels/lev_001.txt");
fileStream.open(file,FileMode.WRITE);
var bytes:ByteArray = new ByteArray();
bytes = brickArrayToByteArray(brVec);
//fileStream.close();
}
private function loadLevelDesign():void{
trace("loadLevelDesign");
var file:File = File.documentsDirectory;
file = file.resolvePath("AnimationFiles/brickLevels/lev_001.txt");
fileStream.open(file,FileMode.READ);
file.addEventListener(IOErrorEvent.IO_ERROR,ioError);
file.addEventListener(Event.COMPLETE, loaded);
file.load();
//fileStream.open(file,FileMode.READ);
}
private function ioError(ioE:IOErrorEvent):void{
trace("oops",ioE);
}
private function loaded(e:Event):void{
trace("loaded");
var bytes:ByteArray = new ByteArray();
fileStream.readBytes(bytes);
trace(bytes.length,"bytes length"); // 0 bytes length
var vec:Vector.<LineTestBlock> = new Vector.<LineTestBlock>;
for (var i:int = 4; i < _playerTurn._brickArray.length; i++){
vec.push(_playerTurn._brickArray[i]);
}
bytes.writeObject(vec);
trace(bytes.length,"bytes length"); // 53516 bytes length
destroyBricks(_playerTurn); // just removes all bricks on the stage
vec = byteArrayToBrickArray(bytes); // calling this function throws all those errors
trace("vector length:",vec.length); // vector length 208 (this is the correct number of bricks, so that's good)
}
My Byte Conversion Methods
private function byteArrayToBrickArray(bytes:ByteArray):Vector.<LineTestBlock>{
bytes.position = 0;
var blocks:Vector.<LineTestBlock> = bytes.readObject() as Vector.<LineTestBlock>;
trace(bytes.position);
return blocks;
}
private function brickArrayToByteArray(brVec:Vector.<LineTestBlock>):ByteArray{
var bytes:ByteArray = new ByteArray();
/*for (var i:int = 0; i < brVec.length; i++){
if (brVec[i]._break == true){
bytes.writeObject(brVec[i]);
}
}*/
bytes.writeObject(brVec);
return bytes;
}
Anyone see if I doing something wrong, or not understanding something?
Any object that implements IExternalizable or is not a DisplayObject can be saved in a ByteArray and restored from one, if you write both readExternal and writeExternal methods correctly. If an object does not implement IExternalizable, Flash will attempt to write it using public components visible to the code, and read it by assigning values read to public properties in the same order. Normally you should use the interface with anything that's more complex than a Vector.<int>. Therefore, you need to implement IExternalizable in your LineTestBlock class, writing and reading only those properties that are required. Also, you can only use this method with objects that have an empty constructor, because in IDataInput.readObject the object is first constructed, then values are assigned.
The manual on IExternalizable. For some reason you can't access it from the normal class tree, but it is there and the interface is working.
I'd change your approach by encapsulating all the vectors, SoundTransforms etc into a single class, say Level, then implement IExternalizable in it, which will then write all the simple data types in order (remember to write vector's lengths before the data!) when asked to, then read itself from a byte array and reconstruct all the internal data structure in the meantime. An example:
import flash.utils.*;
public class Level implements flash.utils.IExternalizable
{
private var blocks:Vector.<LineTestBlock>;
// something extra
public function writeExternal(output:IDataOutput):void {
var l:int=blocks.length;
output.writeInt(l);
for (var i:int=0;i<l;i++) {
//write all the blocks[i]'s properties in order to output
}
// same approach to write all the extra properties
}
public function readExternal(input:IDataInput):void {
var l:int=input.readInt();
blocks=new Vector.<LineTestBlock>();
for (var i:int=0;i<l;i++) {
// first read all the properties into any local variables in the VERY SAME order they were written
// then create an instance of LineTestBlock
var block:LineTestBlock=new LineTestBlock(...);
// required parameters for the constructor should be read before creating object
// then fill remaining properties to the created instance
blocks.push(block); // and reconstruct the array
}
// same approach to every property that was saved
// reconstruct everything else that's depending on the data read
}
}
And finally, you would likely need to perform a flash.net.registerClassAlias() call somewhere in your app's initialization to have your Level be recognized as a serializable class.
bytes.readObject() return an Object.
so problem is about convertin Object to Vector.<LineTestBlock> so you have to convert it your self
private function byteArrayToBrickArray(bytes:ByteArray):Vector.<LineTestBlock>{
bytes.position = 0;
// Edit : readObject() only presents an Object
var blocks:Object = bytes.readObject();
trace(bytes.position);
/* you have to convert all step by step
at first we have to assume blocks as a vector
best way to searching its items is using _for key in_
*/
var converted:Vector.<LineTestBlock> = new Vector.<LineTestBlock>(blocks.length);
for (var key:String in blocks) {
converted.push(objectToLineTestBlock(blocks[key]));
}
return converted;
}
as i dont know structure of your LineTestBlock class, i cant provide "objectToLineTestBlock" function exactly
Here is an Example that simulates your LineTestBlock Class
my own LineTestBlock Class
public class LineTestBlock
{
public var w:int;
public var loc:Point;
public var stf:SoundTransform;
public function LineTestBlock(_w:int, _loc:Point, _stf:SoundTransform)
{
w = _w;
loc = _loc;
stf = _stf;
}
}
main class that testing the solution.
what i do is just converting all Objects to what really they are
bytearray.readObject() convert all classes to pure Objects
public class ByteTest extends Sprite
{
public function ByteTest()
{
var save_vector:Vector.<LineTestBlock> = new Vector.<LineTestBlock>();
var block_item1:LineTestBlock = new LineTestBlock(200, new Point(-1, 1), new SoundTransform(0.5));
var block_item2:LineTestBlock = new LineTestBlock(400, new Point(-2, 2), new SoundTransform(0.25));
save_vector.push(block_item1);
save_vector.push(block_item2);
var load_vector:Vector.<LineTestBlock>;
var bytes:ByteArray = new ByteArray();
bytes.writeObject(save_vector);
// trace(bytes.position);
load_vector = objectToLineTestVector(bytes);
// now test to check if everything is OK
trace(load_vector[1].stf.volume); // must print 0.25
}
public function objectToLineTestVector(bytes:ByteArray):Vector.<LineTestBlock> {
bytes.position = 0;
var loadedObject:Object = bytes.readObject();
var blocks:Vector.<LineTestBlock> = new Vector.<LineTestBlock>();
for (var key:String in loadedObject) {
blocks.push(objectToLineTestBlock(loadedObject[key])); // loadedObject[key] is a block_item1 and could be converted
}
return blocks;
}
public function objectToLineTestBlock(obj:Object):LineTestBlock {
return new LineTestBlock(obj.w, objectToPoint(obj.loc), objectToSoundTransform(obj.stf));
}
public function objectToPoint(obj:Object):Point {
return new Point(obj.x, obj.y);
}
public function objectToSoundTransform(obj:Object):SoundTransform {
return new SoundTransform(obj.volume);
}
}

Using SharedObject with complicated classes

I know that if I want to store a custom class with SharedObject, I have to use registerClassAlias.
registerClassAlias("MyClass", MyClass);
sharedObject.data.myObject = new MyClass();
But in my case, I have a custom class whose fields are themselves instances of custom classes. How can I store it in such a way as to recover the types when I load the data?
Specifically, the class in question is a Graph class which contains an array of Objects. This isn't the actual code, just an overview:
class Graph {
public var vertices : Array;
}
I have an instance of this Graph class, and I'm filling its vertices field with instances of another class, called Node. I need to store this Graph instance in such a way that I can:
Recover it as a Graph instance.
Access the vertices field of this recovered instance, and then access the elements of that array as Node types.
I've tried throwing some registerClassAlias("Node", Node)'s in appropriate-seeming places, but it's not having any effect. Is there a way to do this?
With more complex data such as this, your best bet is to define load() and save() methods manually on objects that you want to store in a SharedObject. Those methods will define what data should be saved, simplified as much as possible, and collate it into a format of your choice such as JSON.
In this example, your Graph could have a method save() which looks like this:
public function save(name:String, sharedObject:SharedObject):void
{
var list:Array = [];
for each(var node:Node in vertices)
{
// Add a simple object defining the important Node properties
// to the array we will save as JSON.
list.push({ x: node.x, y: node.y });
}
sharedObject.data[name] = JSON.encode(list);
}
And then a load() function like so:
public function load(name:String, sharedObject:SharedObject):void
{
// Empty current list of vertices.
vertices = [];
var list:Array = JSON.decode(sharedObject.data[name]);
for each(var def:Object in list)
{
// Create real Node from simpler definition.
var node:Node = new Node();
node.x = def.x;
node.y = def.y;
vertices.push(node);
}
}
Which would be utilized like:
existingGraph.save('myGraph', sharedObject);
var newGraph:Graph = new Graph();
newGraph.load('myGraph', sharedObject);

converting element by using AS operator

So im creating something that now is finished and i want not to create every time elements, but to Pool them (ObjectPooling)
The problem comes that my object from the pool doesnt have the variable from the class it mimics, or at least i understand it that way, cause it doesnt do what it should.
Can someone tell me does this
var myNewBox:Box = Pool_myBox.getSprite() as Box;
mean that all the proparties and parameters that the class Box() has will be given and can be used by the new variable myNewBox or it`s a little more tricky that this?
or in other words is var myNewBox:Box = new Box();
the same as
var myNewBox:Box = Pool_myBox.getSprite() as Box;
------------EDIT-----------
so i do private var Pool_myBox:SpritePool; in the Main Class .
and set Pool_myBox = new SpritePool(Bullet,20); so in the 1st moment it has generated 20 objects.
The whole SpritePool class is this
package {
import flash.display.DisplayObject;
public class SpritePool {
private var pool:Array;
private var counter:int;
private var classRef:Class;
public function SpritePool(type:Class, len:int) {
pool = new Array();
counter = len;
classRef = type;
var i:int = len;
while (--i > -1) {
pool[i] = new type();
}
}
public function getSprite():DisplayObject {
if (counter > 0) {
return pool[--counter];
} else {
increasePool(10);
return getSprite();
}
}
public function increasePool(amount:int):void {
counter += amount;
while( --amount > -1 )
pool.unshift ( new classRef() );
}
public function returnSprite(s:DisplayObject):void {
pool[counter++] = s;
//trace(pool.length)
}
}
}
Absolutely not. If your getSprite() method does not return a Box instance (or some descendant of it), it will not 'inherit' the properties of Box. as is not performing any kind of internal magic - it is simply casting and telling the compiler that you know what you are doing and that the object indeed is a XXX instance (fill in). You should use casting only when going from a more general type to a more specific type, let's assume this:
var child:Sprite = parent.getChildAt(0); //what does this return? A display object instance => compiler will throw an error as Sprite is not DisplayObject
/*
Implicit coercion of a value with static type flash.display:DisplayObject to a possibly unrelated type flash.display:Sprite.
*/
//so you cast it:
var child:Sprite = parent.getChildAt(0) as Sprite; //this won't throw anything cos you casted it correctly
Also note that:
myObj as MyObj
is the same as:
MyObj(myObj)
if Pool_myBox.getSprite returns only Box objects, then you don't need to cast. The getSprite function should be look something like:
public function getSprite():Box {
var recycled_box:Box = ... // get box from pool
return recycled_box;
}
var myNewBox = Pool_myBox.getSprite();
Then, myNewBox will look and act like a Box. Note that any initialization or processing that happened on previous Box instances must be undone when it's returned to the pool if you need a "fresh" instance of Box.
OK, given the pool class, it looks like it should work with casting. Note that your text says you're passing in "Bullet" as the Class, while your code seems to want Box's (I assume this is either a typo, or Bullet is a superclass of Box?). If it works on the first 20, but not after you start recycling, then check what may need to be undone (as above).
What behavior are you seeing that makes you think it's not returning the right Class?

Unable to save class objects to a SharedObject FIle

Well basically as the title implies, I can't save my array to the shared object.
I have an array which contains different "soldiers" with different characteristics(Health,Armor,Weapon,Position,Exp,Level) etc etc and was wondering how I would go about saving it. When I reload the swf I get this trace (",,,") but before I reload it I get a correct array reading.
This is my code if it helps:
//Saving game
function saveGame(E:MouseEvent){
var so:SharedObject = SharedObject.getLocal("saveFile"); //Instantiating the shared object
so.data.savedUnitArray = towerDefenceMain.unitArray;// is the array that stores the Soldiers
trace(so.data.savedUnitArray); //returns correct trace
so.flush();//Saving the operation
}
//Loading the data back
var so:SharedObject = SharedObject.getLocal("saveFile");
if(so.data.savedUnitArray != undefined){
unitArray = so.data.savedUnitArray;
trace(unitArray); //returns (",,,,")
}
In order to save a custom object, you either have to make all its properties public and accessible, AND have no references to DisplayObjects, or implement IExternalizable and define writeExternal() and readExternal() methods. Note that if your object is read from elsewhere, it first is initialized via zero parameter call to its constructor, and then the instance's readExternal() is invoked.
The manual on IExternalizable
An example:
public class Tower2 extends Obstacle implements gameRunnable,IExternalizable {
// HUGE set of statistics skipped, all methods skipped
public function writeExternal(output:IDataOutput):void {
output.writeInt(gemType);
output.writeInt(gemGrade);
output.writeBoolean(affectsFlying); // as some gems might be elongated, saving this
output.writeInt(_targetMode); // placeholder for targetting
output.writeInt(kills);
// hehe, what else to write in here? Everything else is derivable
}
public function readExternal(input:IDataInput):void {
var gt:int = input.readInt();
var gg:int = input.readInt();
MakeGem(gt, gg); // this is the function that initializes everything that's the tower
raised = true; // will place manually if ever
affectsFlying = input.readBoolean();
gt = input.readInt();
SetTargetting(gt);
kills = input.readInt(); // kills
updateDamage(); // this updates damage respective to kills counter
}
So, for your Soldiers you only need to save essential data, and re-create everything else once you load your set of soldiers from the shared object.

Restoring custom class object array from SharedObject

I have an array of Widgets (a class I created) called "widgetArray" that I save into a shared object.
savedGame1.data.widgetArray = widgetArray;
When I go to load this data out and use the widgets I run into problems. The array is stored fine, but each widget is saved as an object. So I can't simply do:
widetArray = savedGame1.data.widgetArray;
because then if I try to use the widgets:
widgetArray[0].someWidgetFunction();
it does not work because flash thinks they are objects. I have tried typecasting the widgets when I load them, but it only produces a null object.
widgetArray[i] = widgetArray[i] as Widget;//widgetArray[i] becomes null
tempWidget:Widget = widgetArray[i] as Widget;//null as well
This question is asked in one of the forums from a friend, since there were no responses, i thought this is the right place for it...
How does anyone else deal with this?
Make two methods that can save and load a widget, the save method should take the data from the widget class and save it into the shared object the load class will take the data from the shared object and set the properties of the object.
Something like this :
public function save():Object
{
return {
"x":this.x,
"y":this.y,
"otherStuff":otherStuff
};
}
public function load(data:Object):void
{
this.x = data.x;
this.y = data.y;
this.otherStuff = data.otherStuff;
}
You can call the save method and store the results in an array and then store it in the shared object. You only need to save the data that is required to rebuild the widget class, not all the properties of the class.
Edit : Updated based on BlueRaja's comment.
As BlueRaja pointed out IExternalizable is meant to be used for this.
If you have a class like this :
public class MyClass implements IExternalizable
{
private var one:int = 1;
private var two:int = 2;
public function MyClass()
{
}
public function writeExternal(output:IDataOutput):void
{
output.writeInt(one);
output.writeInt(two);
}
public function readExternal(input:IDataInput):void
{
one = input.readInt();
two = input.readInt();
}
public function print():void
{
trace(one);
trace(two);
}
}
Then you can save it like this:
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var myClass:MyClass = new MyClass();
sharedObject.data['storedObject'] = myClass;
sharedObject.flush();
Then to load it :
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var loadedClass:MyClass = sharedObject.data['storedObject'] as MyClass;
loadedClass.print();
Hope that helps.
http://tush.wordpress.com/2007/07/08/actionscript-3-serializing-classes-using-registerclassalias/ ... This is also a genuine way to store the custom class objects in shared objects