How to free memory after use of FileRerence? - actionscript-3

See [Solution]
FileReference.load(); does not have a function to unload, just as there is new Loader ().unload();.
Must be a "BUG" from Flash or FileReference needs improvement, type in a new version add a function like this: FileReference.unload();
Or am I mistaken and exists a SOLUTION?
I tried to set "NULL" to a variable of type FileReference, but clearly this does not work for the Flash works with GC (garbage collector), but this is not the focus of the question.
The problem is that a lot of memory when loading multiple files with new FileReferenceList is necessary, but I can not free memory after the process.
How to free memory after use of FileRerence?
See my code:
Main.as
package {
import com.mainpackage.LoaderTestCase;
import flash.net.FileReferenceList;
import flash.net.FileReference;
import flash.net.FileFilter;
import flash.events.Event;
import flash.display.MovieClip;
public class Main extends MovieClip {
private var listFiles:Array;
private var allTypes:Array;
private var fileRef:FileReferenceList;
private var test:int;
public function Main()
{
test = 0;
listFiles = [];
allTypes = [];
fileRef = new FileReferenceList();
fileRef.addEventListener(Event.SELECT, select);
fileRef.browse(allTypes);
}
private function select(e:Event):void
{
listFiles = fileRef.fileList;
for(var i:uint=0, j:uint=listFiles.length; i<j; i++)
{
insert(i);
}
}
private function insert(c:int):void
{
var fire:LoaderTestCase = new LoaderTestCase(listFiles[c]);
fire.destroy(function():void
{
//Delete LoaderTestCase after timeout ???
fire = null;
test++;
if(test>=listFiles.length) {//Remove FileReference
fileRef.removeEventListener(Event.SELECT, select);
fileRef = null;
for(var i:uint=0, j:uint=listFiles.length; i<j; i++) {
listFiles[i] = null;
}
listFiles = null;
trace("Clear memory");
}
});
}
}
}
LoaderTestCase.as
package com.mainpackage
{
import flash.net.FileReference;
import flash.events.Event;
import flash.display.Loader;
public class LoaderTestCase
{
private var file:FileReference;
private var loader:Loader;
private var callback:Function;
public function LoaderTestCase(e:FileReference)
{
file = e;
trace("OPEN: " + file.name);
file.addEventListener(Event.COMPLETE, loadFile);
file.load();
e = null;
}
public function loadFile(e:Event):void
{
file.removeEventListener(Event.COMPLETE, loadFile);
trace("LOAD: " + file.name);
file = null;
e = null;
callback();
}
public function destroy(a:Function):void
{
callback = a;
}
}
}

Remove the event listeners before you null the listening object.
You could use weak references to let the listeners be removed when the object is Garbage Collected.
object.addEventListener( ......, ......., false, 0, true );
for example, in your LoadFile function:
...
LoadFile(file);
}
});
...
should be:
...
LoadFile(file);
}
}, false, 0, true );
...
Or you will have to remove them manually.
To do that you will need to move the event handlers into new named functions.
Also you will need an array for storing the references to listeners and listening objects, to be able to remove the listeners AFTER the listeners are not needed any more and BEFORE nulling the listening object.
PLEASE NOTE:
When you are testing it and watching the current memory usage, make sure to force the Garbage Collector when you feel the memory usage should have dropped by now, but it didn't.
GC kicks in when it wants and very not necessarily after something has been nulled on unloaded.
To be clear, I am only talking about forcing GC during the development/testing.

Even if you null every reference to an object, it won't be deleted immediately from the memory. You have to remove the event listeners aswell. Also, never use "unnamed" functions... it is harder to remove a listener when the event calls an unnamed function. So create a new function, and call that one. For example:
test.contentLoaderInfo.addEventListener(Event.COMPLETE, contentLoaderInfoComplete);
...
function contentLoaderInfoComplete(e:Event){
test.contentLoaderInfo.removeEventListener(Event.COMPLETE, contentLoaderInfoComplete);
test.unload();
test = null;
}
This will clean the memory.

I reached my goal, if I so did FileReferenceList.fileList[5] = null; (when the "sixth file" is not being used more) Flash memory immediately frees this specific FileReference.
In the others words:
This not work:
private var file:FileReference;
...
file = FileReferenceList.fileList[5];
...
file = null;
But this worked:
FileReferenceList.fileList[5] = null;
Worked on all Desktop/Plugins/PepperFlash.
See worked code:
package {
import flash.net.FileReferenceList;
import flash.net.FileReference;
import flash.net.FileFilter;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.display.Sprite;
public class Main extends Sprite
{
private var listFiles:Array;
private var allTypes:Array;
private var fileRef:FileReferenceList;
private var tmpFile:FileReference;
private var i:uint=0;
private var j:uint=0;
private var timer:uint;
private var imageTypes:FileFilter;
private var enable:Boolean;
public function Main()
{
imageTypes = new FileFilter(
"Images (*.JPG;*.JPEG;*.JPE;)", "*.jpg; *.jpeg; *.jpe;"
);
listFiles = [];
allTypes = [imageTypes];
eventBrowse(true);
}
private function eventBrowse(a:Boolean):void
{
enable = a;
if(a===true) {
stage.addEventListener(MouseEvent.CLICK, browse);
fileRef = new FileReferenceList();
fileRef.addEventListener(Event.SELECT, select);
} else {
fileRef.removeEventListener(Event.SELECT, select);
fileRef = null;
stage.removeEventListener(MouseEvent.CLICK, browse);
}
}
private function browse(e:MouseEvent):void
{
if(enable===true) {
fileRef.browse(allTypes);
}
}
private function select(e:Event):void
{
listFiles = fileRef.fileList;
eventBrowse(false);
i=0;
j=listFiles.length;
if(j>0) {
loadNextFile();
}
}
private function loadNextFile():void
{
if(!(i<j)) {
listFiles = null;
trace("Free memory???");
trace("--------------");
trace("listFiles:"+ listFiles);
trace("allTypes:" + allTypes);
trace("fileRef:" + fileRef);
trace("tmpFile:" + tmpFile);
trace("i:" + i);
trace("j:" + j);
trace("timer:" + timer);
trace("--------------");
eventBrowse(true);
return;
}
tmpFile = listFiles[i];
trace("Initiate load:" + tmpFile.name);
tmpFile.addEventListener(Event.COMPLETE, loadedFile);
tmpFile.load();
}
private function loadedFile(f:Event):void
{
trace(listFiles);
trace("Finished load:" + tmpFile.name);
tmpFile.removeEventListener(Event.COMPLETE, loadedFile);
tmpFile = null;
listFiles[i] = null;
i++;
loadNextFile();
}
}
}

The issue is a combination of all the things noted above.
You do need to remove the event listener(s) manually. While its possible to use weak references its better if you make a habit of keeping track of the listeners you register and always unregister them properly. This way you can better avoid memory leaks(not quite a memory leak but has a similar effect) you didn't expect or weren't thinking about.
You are creating event listeners in a loop and re-using the same function to handle all of them. If you do this you must somehow get a reference to the original loader and remove the event listener from it. I have no idea how you tried to incorporate Zhafur's answer but if you re-used file reference for each new file that will be the reason its still not working. Perhaps you can update you example above with the code you currently have so we can critique further.
You should never force the gc(garbage collector), if you need to do this you have issues elsewhere you should solve instead as san.chez mentioned. Forcing the GC is a good way to see that you are creating too many objects too fast, if you see your memory usage go way down after forcing the GC you probably did this and should rewrite your code to be more efficient in its use of new.
Judging by the amount of memory you have consumed your either creating a ton of small files or a few extremely large ones, perhaps you can tell us more about that as well.

Related

How to return a variable from an actionscript Event? Loading XML file

i'm trying to load an xml file from my assets folder.
I wrote this function :
public static function loadXML(i_fileURL:String):XML // i want to return the actual loaded xml here
{
var xml:XML;
var ldr:URLLoader = new URLLoader();
var request:URLRequest = new URLRequest(i_fileURL);
ldr.addEventListener(Event.COMPLETE, onXMLLoad);
ldr.load(request);
//how can i return the loaded xml?
}
public static function onXMLLoad(e:Event):void
{
var ldr:URLLoader = URLLoader(e.target);
var myxml:XML = new XML(ldr.data);
trace(myxml.toXMLString());
//how can i return myxml to loadXML function?
}
Is there a different way to do this?
Thank you!
You can do something like a promise or future, where you return empty XML and then populate it with the actual XML when the call returns. Since you are using Flex, you have access to data binding, which should allow this approach to work just fine.
Note that you really shouldn't be using static methods for this, and your onXMLLoad member has no reason to be exposed. Here's what the updated code might look like:
package service {
public class XMLLoader {
//note that the existence of this variable means that you need
//to create a new instance of the Class each time you make a call.
protected var future:XML;
protected var _url:String;
public function loadXML(url:String):XML {
_url = url;
var request:URLRequest = new URLRequest(url);
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.Complete, onLoad);
loader.addEventListener(IoErrorEvent.IO_Error, onFail);
loader.addEventListener(SecurityErrorEvent.Security_Error, onFail);
future = ;
return future;
}
protected function onLoad(e:Event):void {
var loader:URLLoader = e.currentTarget as URLLoader;
var data:XML = loader.data as XML;
if (data) {
//you lose your original root node, because you want data
//binding to fire on the future object you returned.
future.setChildren(data.children());
}
}
protected function onFail(e:Event):void {
//both Security and IOerrors have a text property, but they
//can't both be cast to the same thing.
trace('failed to load', _url, e[text]);
}
}
}
One thing to be aware of with this method is that you need to keep a reference to the instance around until the data has updated, or else it could be garbage collected before it populates the future. Because of that, you're probably better off following the conventional approach of having your instance dispatch a custom event that carries the data that it was retrieving. If you want an example of that, post back and I can provide you an example.
you can't. because xml load request is asynchronous. when you called loadXML, onXMLLoad not yet arrived. so such work impossible asynchronous return. surely you can waiting the function process while loop using, but this method not good. because to full use a cpu resource, overkill. you must next process in onXMLLoad function. It's the most appropriate. or xml variable declared as global, and using a ENTER_FRAME or TimerEvent as a way to continue to operate if the xml is not null.
Since the URLLoader is asynchronous, it's not safe to create a static loader function as it would be quiet easy to mix up returned data during multiple calls. Even if you attempted to accomplish what you want with the use of Event.OPEN and a vector of URLs to keep track of the which completed data should belong to each URL, asynchronousity works on a first-come, first-served basis so it wouldn't be possible to have persistent alignment of the file URL and the returned data.
I suggest that you create instances of an XMLLoader class that uses a custom XMLLoaderEvent, which will return both the xml data and the associated file URL. The following code is untested, but with possible typos aside, it should work as intended.
Use Case
var xmlLoader:XMLLoader = new XMLLoader();
xmlLoader.addEventListener(XMLLoaderEvent.COMPLETE, xmlLoadCompleteEventHandler);
xmlLoader.load("myXMLFile.xml");
function xmlLoadCompleteEventHandler(event:XMLLoaderEvent):void
{
xmlLoader.removeEventListener(XMLLoaderEvent.COMPLETE, xmlLoadCompleteEventHandler);
trace(event.type, event.fileURL, event.xml);
}
XMLLoader.as
package
{
//Imports
import flash.events.EventDispatcher;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
//Class
public class XMLLoader extends EventDispatcher
{
//Properties
private var loader:URLLoader;
private var fileURL:String;
//Constructor
public function XMLLoader():void
{
loader = new URLLoader();
loader.addEventListener(Event.COMPLETE, loadCompleteEventHandler);
}
//Load
public function load(fileURL:String):void
{
this.fileURL = fileURL;
loader.load(new URLRequest(fileURL));
}
//Load Complete Event Hanlder
private function loadCompleteEventHandler(event:Event):void
{
loader.removeEventListener(Event.COMPLETE, loadCompleteEventHandler);
dispatchEvent(new XMLLoaderEvent(XMLLoaderEvent.COMPLETE, fileURL, XML(loader.data)));
}
}
}
XMLLoaderEvent.as
package
{
//Imports
import flash.events.Event;
//Class
public class XMLLoaderEvent extends Event
{
//Constants
public static const COMPLETE:String = "complete";
//Properties
public var xml:XML;
public var fileURL:String;
//Constructor
public function XMLLoaderEvent(type:String, fileURL:String = null, xml:XML = null)
{
super(type);
this.xml = xml;
this.fileURL = fileURL;
}
//Clone
public override function clone():Event
{
return new XMLLoaderEvent(type, fileURL, xml);
}
//To String
public override function toString():String
{
return formatToString("XMLLoaderEvent", "type", "fileURL", "xml");
}
}
}

Actionscript 3 - checking for an internet connection

I am using this code in my flash file
import air.net.URLMonitor;
import flash.net.URLRequest;
import flash.events.StatusEvent;
var monitor:URLMonitor;
function checkInternetConnection(e:Event = null):void
{
var url:URLRequest = new URLRequest("http://www.google.com");
url.method = "HEAD";
monitor = new URLMonitor(url);
monitor.pollInterval = 1000;
//
monitor.addEventListener(StatusEvent.STATUS, checkHTTP);
//
function checkHTTP(e:Event = null):void
{
if (monitor.available) {
navigateToURL(new URLRequest("flickr.html"),"_top");
} else {
gotoAndPlay(1);
}
}
monitor.start();
}
I am trying to get the flash to check for a connection and navigate to another page if not it will replay.
It doesnt seem to be working.
Am i missing anything?
I have add the library path to aircore.swc also.
Its meant to be a html page with flash rather than an AIR app
Cheers
(My reputation is too low to comment on Tianzhen Lin's answer directly ...)
I needed to make a few changes in order to get Tianzhen Lin's answer to work as expected:
Added:
urlRequest.useCache = false;
urlRequest.cacheResponse = false;
This addition was required because even when the connection was definitely lost, the check was still succeeding because the cache was being read from.
Changed:
if( textReceived.indexOf( _contentToCheck ) )
to:
if( !(textReceived.indexOf( _contentToCheck ) == -1) )
This change was required because while "" (an empty string) was always being found, it was being found at index '0' which was causing the original if() condition to always fail.
Added:
urlRequest.idleTimeout = 10*1000;
In the case where the network cable was physically disconnected from the router, the request could take a very long time to time-out (Honestly, I got tired of waiting after a couple of minutes.)
After making the above changes, I found Tianzhen's code worked perfectly: No matter how I went about disconnecting/reconnecting Wi-Fi (on both iOS and Android devices) the change in connection status was always detected.
I see two issues in your code. One is that while you have the logic to check internet connection, there is not any code calling the function, therefore the logic of redirection would not be called. Second is that using AIRcore.swc would be a bad idea as you have injected a dependency that might not work with or violate browser sandbox.
You may try the following approach which is tested and requires no AIR's SWC:
Step 1, include a new class ConnectionChecker as follow:
package
{
import flash.events.*;
import flash.net.*;
[Event(name="error", type="flash.events.Event")]
[Event(name="success", type="flash.events.Event")]
public class ConnectionChecker extends EventDispatcher
{
public static const EVENT_SUCCESS:String = "success";
public static const EVENT_ERROR:String = "error";
// Though google.com might be an idea, it is generally a better practice
// to use a url with known content, such as http://foo.com/bar/mytext.txt
// By doing so, known content can also be verified.
// This would make the checking more reliable as the wireless hotspot sign-in
// page would negatively intefere the result.
private var _urlToCheck:String = "http://www.google.com";
// empty string so it would always be postive
private var _contentToCheck:String = "";
public function ConnectionChecker()
{
super();
}
public function check():void
{
var urlRequest:URLRequest = new URLRequest(_urlToCheck);
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.addEventListener(Event.COMPLETE, loader_complete);
loader.addEventListener(IOErrorEvent.IO_ERROR, loader_error);
try
{
loader.load(urlRequest);
}
catch ( e:Error )
{
dispatchErrorEvent();
}
}
private function loader_complete(event:Event):void
{
var loader:URLLoader = URLLoader( event.target );
var textReceived:String = loader.data as String;
if ( textReceived )
{
if ( textReceived.indexOf( _contentToCheck ) )
{
dispatchSuccessEvent();
}
else
{
dispatchErrorEvent();
}
}
else
{
dispatchErrorEvent();
}
}
private function loader_error(event:IOErrorEvent):void
{
dispatchErrorEvent();
}
private function dispatchSuccessEvent():void
{
dispatchEvent( new Event( EVENT_SUCCESS ) );
}
private function dispatchErrorEvent():void
{
dispatchEvent( new Event( EVENT_ERROR ) );
}
}
}
Step 2, in your main application or anywhere that internet connection should be checked, use the following snippet:
var checker:ConnectionChecker = new ConnectionChecker();
checker.addEventListener(ConnectionChecker.EVENT_SUCCESS, checker_success);
checker.addEventListener(ConnectionChecker.EVENT_ERROR, checker_error);
checker.check();
private function checker_success(event:Event):void
{
// There is internet connection
}
private function checker_error(event:Event):void
{
// There is no internet connection
}
The air.net.URLMonitor is the AIR available class - so will not work outside of the AIR player.
But you could try to do your own as all this monitor class does is "ping" the url and check the response, so you could do similar try to load something from known source like google.com and if it completes wihtout error then it is OK otherwise you will get error.

Trying to delete child instance in array. Lack CS training. Totally stuck

I posted yesterday about how to communicate to one class from another that I wanted to delete an instance of it, and I got the dispatcher working today. However, I think I've painted myself into a corner. Even though the dispatcher is working, I A:feel like it's running through too many functions on the way to actually deleting the object, and B: still can't manage to get it to actually delete. I don't have any formal CS training, so it's one of those situations where my mind is going in circles and I can't "see" what I'm doing wrong. I figure if I post my classes here, at the very least people can have a chuckle at my amateur code, and if I'm lucky, some kind soul will point out what I'm doing wrong. So here goes:
Background.as:
//Background class. Singleton? Sets up/maintains the application.
package pc_mockup {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class Background extends flash.display.MovieClip {
private var slate:MovieClip;
private var slateBounds:Rectangle = new Rectangle(100,-260,0,280);
private var _toolbox:MovieClip;
private var _elementArray:Array = new Array();
public function Background() {
//attach movieclips to stage
slate = new mc_slate();
slate.x = 100;
slate.y = 20;
addChild(slate);
_toolbox = new Toolbox();
_toolbox.x = 750;
_toolbox.y = 20;
addChild(_toolbox);
//set draggables
//slate.addEventListener(MouseEvent.MOUSE_DOWN, dragSlate);
//slate.addEventListener(MouseEvent.MOUSE_UP, releaseSlate);
slate.addEventListener(MouseEvent.MOUSE_UP, dropNewElement);
}
private function dragSlate(event:MouseEvent) {
slate.startDrag(false, slateBounds);
}
private function releaseSlate(event:MouseEvent) {
slate.stopDrag();
}
private function dropNewElement(event:MouseEvent) {
var _elementType:String = _toolbox.currentTool;
var _x:Number = event.target.x;
var _y:Number = event.target.y;
var _newElement:MovieClip;
var _latestIndex:Number;
//case switch to choose element based on _elementType
//add new element to stage
_newElement = new PageElement(_elementType, event.localX, event.localY);
_latestIndex = _elementArray.push(_newElement);
_newElement.addEventListener("closeWindow", deleteElement);
slate.addChild(_newElement);
}
private function deleteElement(event:Event) {
trace("trying to remove element.");
slate.event.target.removeChild(_elementArray[0]);
}
}
}
Toolbox.as:
//Toolbox class.
package pc_mockup {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class Toolbox extends flash.display.MovieClip {
private var _toolboxback:MovieClip;
private var _tool01:MovieClip;
private var _tool02:MovieClip;
private var _tool03:MovieClip;
private var _tool04:MovieClip;
private var _tool05:MovieClip;
private var _currentTool:String = 'none';
public function Toolbox() {
_toolboxback = new ToolboxBack();
_toolboxback.x = 0;
_toolboxback.y = 0;
_toolboxback.alpha = .5;
addChild(_toolboxback);
_tool01 = new TextTool();
_tool01.x = 10;
_tool01.y = 10;
addChild(_tool01);
//_tool01.addEventListener(MouseEvent.MOUSE_DOWN, dragTool);
_tool01.addEventListener(MouseEvent.MOUSE_UP, switchTool);
_tool02 = new ImageTool();
_tool02.x = 10;
_tool02.y = 54;
addChild(_tool02);
_tool02.addEventListener(MouseEvent.MOUSE_UP, switchTool);
}
private function dragTool(event:MouseEvent) {
event.target.startDrag(false);
}
private function releaseTool(event:MouseEvent) {
event.target.stopDrag();
}
private function switchTool(event:MouseEvent) {
_currentTool = event.target.toolname;
//trace(_currentTool);
}
public function get currentTool():String{
return _currentTool;
}
}
}
Tool.as (any class with "Tool" at the end of it simply extends this class and adds a name)
//Tool class.
package pc_mockup {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class Tool extends flash.display.MovieClip {
private var _toolname:String;
public function Tool(toolname) {
_toolname = toolname;
}
public function get toolname():String{
return _toolname;
}
}
}
PageElement.as:
//Page element class.
package pc_mockup {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.text.*;
public class PageElement extends flash.display.MovieClip {
private var _elementname:String;
private var _elementback:MovieClip;
private var _elementmenu:MovieClip;
private var _title:TextField;
private var _formatter:TextFormat = new TextFormat();
public function PageElement(elementname, x, y) {
_elementname = elementname;
_elementback = new ElementBack();
_elementback.x = x;
_elementback.y = y;
_elementback.alpha = .5;
_elementback.addEventListener(MouseEvent.MOUSE_DOWN, dontBubble);
_elementback.addEventListener(MouseEvent.MOUSE_UP, dontBubble);
_elementmenu = new ElementMenu();
_elementmenu.x = x + _elementback.width - 5;
_elementmenu.y = y - 5;
_elementmenu.addEventListener(MouseEvent.MOUSE_OVER, showElementMenu);
_elementmenu.addEventListener(MouseEvent.MOUSE_OUT, retractElementMenu);
_elementmenu.addEventListener(MouseEvent.MOUSE_DOWN, dragElement);
_elementmenu.addEventListener(MouseEvent.MOUSE_UP, releaseElement);
_formatter.font = "Helvetica";
_formatter.size = 10;
_title = new TextField();
_title.text = elementname;
_title.x = x;
_title.y = y;
_title.textColor = 0xffffff;
_title.setTextFormat(_formatter);
addChild(_title);
addChild(_elementback);
addChild(_elementmenu);
}
public function get elementname():String{
return _elementname;
}
public function set elementTitle(newTitle) {
}
public function showElementMenu(event:MouseEvent) {
_elementmenu.expandMenu();
}
public function retractElementMenu(event:MouseEvent) {
_elementmenu.retractMenu();
}
public function hideElementMenu() {
_elementmenu.alpha = 0;
}
private function dragElement(event:MouseEvent) {
event.target.parent.parent.startDrag(false);
event.stopPropagation();
}
private function releaseElement(event:MouseEvent) {
event.target.parent.parent.stopDrag();
event.stopPropagation();
}
private function dontBubble(event:MouseEvent) {
event.stopPropagation();
}
}
}
DeleteBack.as:
//Element menu back class.
package pc_mockup {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class DeleteBack extends flash.display.MovieClip {
public function DeleteBack() {
}
public function closeElement(event:MouseEvent) {
dispatchEvent(new Event("closeWindow", true));
trace("event dispatched.");
}
}
}
ElementMenu.as:
//Element menu class.
package pc_mockup {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import caurina.transitions.Tweener;
public class ElementMenu extends flash.display.MovieClip {
private var _elementmenuback:MovieClip;
private var _deletebutton:MovieClip;
public function ElementMenu() {
_elementmenuback = new ElementMenuBack();
_elementmenuback.x = 0;
_elementmenuback.y = 0;
_elementmenuback.width = 100;
_elementmenuback.height = 5;
_elementmenuback.alpha = .5;
addChild(_elementmenuback);
_deletebutton = new DeleteBack();
_deletebutton.x = -5;
_deletebutton.y = 10;
_deletebutton.width = 10;
_deletebutton.height = 10;
_deletebutton.alpha = .2;
_deletebutton.visible = false;
addChild(_deletebutton);
_deletebutton.addEventListener(MouseEvent.MOUSE_DOWN, closeElement);
}
public function expandMenu() {
Tweener.addTween(_elementmenuback, {height:30, time:.2, transition:"easeOutBack"});
_deletebutton.visible = true;
}
public function retractMenu() {
Tweener.addTween(_elementmenuback, {height:5, time:.1, transition:"easeInBack"});
_deletebutton.visible = false;
}
public function closeElement(event:MouseEvent) {
//check that the user really wants to close the element before sending the destroy signal
//perform any closing animations
//this.parent.destroy();
_deletebutton.closeElement(event);
}
}
}
That's it for the meaningful classes. Anything else is either an empty class that's only in there to help make the library object accessible to ActionScript, or a trivial extension of something else.
The code puts a new element onto the stage, gives it a cool little dropdown menu that makes it draggable and has a delete button on it, and should link that button to a function that closes the element.
I've got everything but the closing.
General code criticism also very welcome. Like I said, I have no training, I've been figuring this stuff out for myself, and feedback of any kind from people who know what they're doing is valuable.
Thanks!
SS
PS. In response to Daniel's comment, here are the steps the code takes:
The Background class puts everything on the stage and creates the toolbox.
The toolbox creates the tools, which are like Photoshop's tools. You click on them to select an element you want to add to the stage, then you click inside the "slate" to drop a new instance of that object on top of it. The background creates the instance and saves it in an array of all instances created at runtime.
The new element makes its own dropdown menu, which is the draggable portion of the element and holds the delete button. This menu places an eventListener on the delete button.
When the delete button is clicked, the eventListener placed on it by its parent class calls an event dispatcher inside the delete button class itself.
This dispatched event is caught by the background class (I figured the best class to remove the element is the same class that made it, right?) and triggers the actual code to remove the element.
This code, "deleteElement," is where I'm stuck. I have all the instances in an array, but the event has gone through so many intermediary classes, the MouseEvent, and thus, I suspect, the MouseEvent target, has fallen by the wayside. So the only way to know which element to delete is to find its array index. I have no idea how this would work. Any ideas?
let's do this a bit at a time...
slate.event.target.removeChild(_elementArray[0]); in background.as
why are you using slate.event?
you are passing an event object to the function, but looks like you're using a different event's target, which I don't know where it's coming from or why it's not giving you an error.
it should just be event.target, which should give you the PageElement(formerly known as _newElement)
what I don't know also is why you are removing a child from it which is _elementArra[0] - which really is another PageElement and likely itself if you only have one.
so it looks to me that there are a bunch of things that should have thrown errors. What are you using to compile your code? What about debugger? are you using any?
If you look at your previous question, I added some code there about how to get the parent. So I adjusted it a bit
function deleteElement($e:MouseEvent):void{
var parentMC:MovieClip = $e.target.parent;
parentMC.removechild($e.target);
}
however the problem is that you're not passing a MouseEvent but a blank event
dispatchEvent(new Event("closeWindow", true)); in DeleteBack.as
so this will not pass anything under target, and you can't get it. (target is read only, so new Event(etc) will always have a null target. So essentially that's a bit of a lost cause.
you could set an onject in your singleton and pass which mc is to be deleted, and then the deleteElement would just grab that object. The other option is to look into the signals class which will let you do some better/more efficient event handling.
finally (sort of, there's more but for now) I'd say look into using CASAlib, in particular, use CasaMovieClip instead of MovieClip for extending, as it will delete your movie clips better. If you have a lot of event listeners and you don't clear them properly, they'll end up staying in memory even after you delete them.
of course looking into other frameworks like RobotLegs is a good idea too, it gets you into better practices.
GL
Edit ...
frameworks/micro-architectures:
http://www.robotlegs.org/
http://swizframework.org/
http://puremvc.org/
and many more
I think the important thing is to not get stuck on a framework (though I mention the word often). And the best framework is the framework that is best for you, and to me that means offering a good communication backbone for the app and staying out of the way.
My setup for writing code is this:
FlashDevelop with the Flex Compiler. FlashDevelop is for PC only so if you're on Mac you might want to consider other options like flex. FlashDevelop and the Flex compiler(the compiler only) are both free so you can't go wrong, and once you start using it you won't want to go back to coding in Flash - guaranteed!!
debugging:
Trace is the simplest form of debugging, and it can be quite difficult to understand the problem.
You can use the flash debugger by pressing Ctrl-Shift-Enter to compile and run. You will need to set the break points ahead though.
FlashDevelop has a debugger that works just like the Flash and Flex debuggers and I use it quite often.
But my favorite debug tool has to be de monster debugger
it takes a bit to more to implement, and you need to add some code, but it found issues for me that I couldn't get to using the default debugger only. Definitely worth a look.

sound stream stops loading

I have a little sound stream script here, but sometimes if you press play to the next track before the current has done loading the next track doesn't comeplete loading
package player {
import flash.events.Event;
import flash.display.Sprite;
import flash.external.ExternalInterface;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
import player.Loader_bar;
public class Stream extends Sprite {
private var _Sound = null;
private var _Channel = null;
private var isLoading = false;
private var _Loader_bar = null;
public var loader_color = null;
public var loader_width = 0;
public var loader_height = 0;
private var i = 0;
public function Stream(){
this._Loader_bar = new Loader_bar();
addChild(this._Loader_bar);
}
public function cnstr(){
this._Loader_bar.color = this.loader_color;
this._Loader_bar.w = this.loader_width;
this._Loader_bar.h = this.loader_height;
this._Loader_bar.cnstr();
}
public function play(url){
this.stop();
this.close();
this._Sound = new Sound();
this.isLoading = true;
this.addEventListener(Event.ENTER_FRAME, listener_bytesLoaded);
this._Sound.addEventListener(Event.COMPLETE, listener_loadedComplete);
this._Sound.load(new URLRequest(url));
this._Channel = this._Sound.play();
}
public function stop(){
if(this._Channel){
this._Channel.stop();
}
}
private function close(){
if(this.isLoading){
this._Sound.close();
}
}
private function listener_loadedComplete(event){
this.close();
this.isLoading = false;
this.removeEventListener(Event.ENTER_FRAME, listener_bytesLoaded);
}
private function listener_bytesLoaded(event){
var float = this._Sound.bytesLoaded / this._Sound.bytesTotal;
this._Loader_bar.progress(float);
var data = {
i : this.i,
float : float,
loaded : this._Sound.bytesLoaded,
total : this._Sound.bytesTotal
};
ExternalInterface.call('swf2js', 'tst_progress', data);
this.i++;
}
}
}
close():void
Closes the stream, causing any download of data to cease.
try this:
create a boolean like hasLoaded, set it to false. When the sound successfully loads set it to true.
Then when you play a sound you can test for hasLoaded in your play() function. If you have called play() before your previous sound has loaded, hasLoaded will be false, in which case you call this._Sound.close() before creating and loading the new sound. The reason for testing instead of just calling close() is so if you have paused the stream you don't have to reload it to play it again.
ADDITION:
Concerning the load not reporting properly, you have your progress logic set up incorrectly. Try this:
1) import flash.events.ProgressEvent
2) For the listener, replace this.addEventListener(Event.ENTER_FRAME, listener_bytesLoaded); in your play() method with this._Sound.addEventListener(ProgressEvent.PROGRESS, listener_bytesLoaded);
3) Change your listener_bytesLoaded() method to the following:
private function listener_bytesLoaded(event:ProgressEvent)
{
var percent = event.bytesLoaded / event.bytesTotal;
this._Loader_bar.progress(percent);
var data = {
i : this.i,
float : percent,
loaded : event.bytesLoaded,
total : event.bytesTotal
};
ExternalInterface.call('swf2js', 'tst_progress', data);
this.i++;
}
4) Change this.removeEventListener(Event.ENTER_FRAME, listener_bytesLoaded);
in your listener_loadedComplete() method to this._Sound.removeEventListener(ProgressEvent.PROGRESS, listener_bytesLoaded); and then move it out of that method and place it inside the conditional in the close() method.
NOTE - I didn't actually compile this but I think its good to go. Hope that helps. :)

Run-time loading of external assets and re-using preloaded assets in actionscript 3?

I'm creating a 2d flash game (coded in flex/actionscript 3) where assets are downloaded when they are needed. Currently I have it setup like this:
AssetLoader.as
package
{
import flash.display.Loader;
import flash.net.URLRequest;
public class AssetLoader extends Loader
{
//set vars
private var url:String = "http://test.com/client/assets/";
public function AssetLoader(url:String)
{
Logger.log("AssetLoader request: " + this.url + url);
var request:URLRequest = new URLRequest(this.url + url);
this.load(request);
}
}
}
Then, where I want to load the asset I do the following:
var asset:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
asset.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete, false, 0, true);
private function onShipAssetComplete(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
shipImage = Bitmap(loader.content);
shipImage.smoothing = true;
addChild(shipImage);
}
The thing is, that this method doesn't check for already downloaded assets, so it will redownload them the second time the same asset is being requested (I think).
So, what I need is an array where all downloaded assets are stored, and on request the name of this asset is checked for existance in the array. So if it has already been downloaded, that asset from memory must be returned rather than redownloaded.
I could make the assetloader a static class, but I have to wait for the event to fire when it's done downloading the image - so I can't simply let a static function return the corresponding image. Any idea how I should do this?
EDIT for an attempt after comments:
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
public final class AssetManager
{
private static var assets:Object = {};
private static var preUrl:String = Settings.ASSETS_PRE_URL;
public static function load(postUrl:String):*
{
if (assets[postUrl])
{ //when the asset already exists
//continue
}
else
{ //the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
assets[postUrl] = loader.content;
}, false, 0, true);
}
}
}
}
EDIT2: another attempt
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLRequest;
public final class AssetManager
{
private static var assets:Object = {};
private static var preUrl:String = Settings.ASSETS_PRE_URL;
public static function load(postUrl:String):*
{
if (assets[postUrl])
{ //the asset already exists
var dispatcher:EventDispatcher = new EventDispatcher();
dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
}
else
{ //the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
assets[postUrl] = loader.content;
var dispatcher:EventDispatcher = new EventDispatcher();
dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
}, false, 0, true);
}
}
}
}
Then, I try the following:
var asset:AssetManager = AssetManager.load("ships/" + graphicId + ".gif");
asset.addEventListener(CustomEvent.LOAD_COMPLETE, onShipAssetComplete, false, 0, true);
But get an error, "undefined method addEventListener by a reference of the type static AssetManager" (roughly translated).
You could add a static object (used as a dictionary with urls for assets as keys and the content for assets as values) in the AssetLoader class and in the same time keep using the class in the way you're using it right now.
private static var assets:Object = {};
The difference would be that your class would need to check against that static object if the URL for the content has already been requested previously. If it has, dispatch the complete event immediately. If it hasn't, follow the normal routine and don't forget to populate your static object with the newly loaded asset.
Update:
This is a quick example of what I meant. I haven't had time to test this, but it should work.
Note:
You must invoke the loadAsset() method of the AssetLoader instances you create in order to actually load the asset. This is consistent with the way the Loader class we're extending works.
You should always add all event listeners BEFORE invoking the loadAsset() method. In your question you're calling the load() method from within the constructor and only afterwards add the event listener for Event.COMPLETE. This could produce strange results.
Here's the code:
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
public class AssetLoader extends Loader
{
private static const BASE_URL:String = 'http://test.com/client/assets/';
public static var storedAssets:Object = {};
private var assetURL:String;
private var urlRequest:URLRequest;
private var cached:Boolean = false;
public function AssetLoader(url:String):void
{
trace('Loading: ' + url);
assetURL = url;
if (storedAssets[assetURL] != null)
{
cached = true;
trace('Cached');
}
else
{
trace('Loading uncached asset');
urlRequest = new URLRequest(BASE_URL + assetURL);
contentLoaderInfo.addEventListener(Event.COMPLETE, OnAssetLoadComplete);
}
}
public function loadAsset():void
{
if (cached)
loadBytes(storedAssets[assetURL]);
else
load(urlRequest);
}
private function OnAssetLoadComplete(event:Event):void
{
storedAssets[assetURL] = contentLoaderInfo.bytes;
trace('Loaded ' + contentLoaderInfo.bytesLoaded + ' bytes');
}
}
}
Update 2:
Here's how one would use the class above:
var assetLdr:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
assetLdr.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete);
assetLdr.loadAsset();
private function onShipAssetComplete(event:Event):void
{
var shipImage:Bitmap = Bitmap(event.target.loader.content);
// Do stuff with shipImage
}
Perhaps you should take a look at Bulk Loader. It does the kinds of things your looking to do. If you really want to use a custom solution, it would be a great point of reference, but why reinvent the wheel?
Tyler.
Here is an alteration of your load command to capture the resourceId
public function load(postUrl:String):*
{
var index:int;
if ((index = assetExists(postUrl)) != -1)
{
dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
}
else
{
//the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event)
{
// NOTE: not sure on scoping here ...
// pretty sure you're this will be the Loader
// if not just read it off the event like you were before
assets[postUrl] = content;
dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
}, false, 0, true);
}
}
/* In a new file */
public class CustomEvent extends Event
{
public static const LOAD_COMPLETE:String = "CustomEvent_LoadComplete";
// If you know the type you should use it (e.g. Sprite/DisplayObject)
public var content:*;
public function CustomEvent(type:String, _content:*)
{
content = _content;
super(type);
}
}
Note: when you write an Event descendant you should also override the toString and clone methods. I've also cheated on the constructor since you may want to pass through weakReferences and things like that.