Asynchronous Lazy Loading in ActionScript - actionscript-3

I am having trouble with implementing this. Basically, I have many large chunks of data that need to load with URLoader. And I want to use lazy loading because eager loading takes up a lot of network resources/time. For example:
class Foo {
private var _resources:Array;
public get_resource(id:String){
if ( _resources[id] == null ){
//Load the resource
}
return _resources[id];
}
}
However, the above code only works with synchronous loading. I don't want the asynchronous being exposed to other parts of the program. How do I do this? Thanks.

Since an asynchronous function returns immediately (before loading is done),
you need to pass a callback function (event handler) to get data from it.
The code may be something like this:
class Foo {
private var _resources:Array;
private var _loader:Array; // holds URLLoader objects
public get_resource(id:String, callback:Function) : void {
if ( _loader[id] == null ){
var ld : URLLoader = new URLLoader();
// instead of returning value, callback function will be
// called with retrieved data
ld.addEventListener(Event.COMPLETE, function (ev:Event) : void {
_resources[id] = ev.target.data;
callback(ev.target.data);
});
ld.load (/* URLRequest here */);
_loader[id] = ld;
}
// return nothing for now
}
}
[edit] removed a wrong comment "// URLLoader object must be held by a class variable to avoid GC". But _loader is still needed for remembering which id is loading.

I'm afraid you can't ... flash player is single threaded (at least for nearly everything that counts), which is why all I/O operations are asynchronous.
the solution presented by torus is about the best you can get.

Related

Possible to listen for event then return result from class?

I have an as3 class that I use for communicating with my Zend_AMF endpoint:
package test
{
import flash.events.Event;
import mx.rpc.AbstractOperation;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
[Bindable]
[RemoteClass(alias="Request")]
public class Request
{
public var url:String = '';
//There are a lot more properties, but I have removed them for brevity
public function Request(url:String){
this.url = url;
}
public function send():void{
//Create the remoting object
var remotingObject:RemoteObject = new RemoteObject('test');
remotingObject.endpoint = "http://site.com/endpoint.php";
remotingObject.showBusyCursor = true;
//Send it
remotingObject.source = this.url;
var op:AbstractOperation = remotingObject.getOperation(null);
op.addEventListener(ResultEvent.RESULT, result);
op.send(this);
}
private function result(event:ResultEvent){
this.dispatchEvent(event);
}
}
}
To use it I do the following:
var request:Request = new Request('someRemotingCommand');
request.addEventListener(ResultEvent.RESULT, catchEvent);
request.send();
public function catchEvent(event:ResultEvent):void{
var result:Response = event.result as Response; //Get result as my response object
trace(result.responseText);
}
All of this works fine, but it takes a lot of work to use it. I always expect a Response object when calling send() on my request.
Ideally I would like to be able to call request.send() and have it return a Response object directly and not having to deal with a listener:
var result:Response = request.send();
Is this possible? If so, how can I go about doing this?
there is a time that elapses or code that needs to complete between an asynchronous event starting and responding.
The as code is not doing anything other that listening for the event to come back, it wouldn't make sense to hang while it is waiting.
If writing all the extra code is too much work for every event-response pair, create a factory class that will handle event creation and removal that would simplify it to one line, but all you're doing is hiding the event handlers, which you can't really avoid when it comes to asynchronous communication.
edit
You could create a class that bundles this into one abstract class, and different classes stemming from this class would handle different commands and responses differently.
var request:Request = new Request('someRemotingCommand');
request.addEventListener(ResultEvent.RESULT, catchEvent);
request.send();
public function catchEvent(event:ResultEvent):void{
var result:Response = event.result as Response; //Get result as my response object
trace(result.responseText);
}
the utility of such a setup really depends on what you're trying to achieve though. With asynchronous calls, it'll always comes back to event handling.

Flex waiting for httpservice to return data

Seriously. I'm getting really annoyed by Flex. Is there a way to make it wait for an httpservice to get its data without having to use a timer?
At the moment my code looks like this:
protected function loginUser(event:MouseEvent):void
{
if(txtEmail.text == "" || txtPassword.text == "")
{
Alert.show("Please complete all fields!", "Oops!");
}
else
{
user = new User(txtEmail.text, txtPassword.text);
user.login(user);
var loginTimer:Timer = new Timer(1000, 1);
loginTimer.addEventListener(TimerEvent.TIMER_COMPLETE, dispatchLoginEvent);
loginTimer.start()
}
}
When I do user.login(), it sends a request with a HTTPservice from my external AS class. In the result event handler for this httpservice, I set a public variable to true or false, depending on whether the user's credentials are correctly in the DB.
I then use a getter to get that boolean value. However, without the timer, it always returns false because my code is faster than the event result handler. If that makes sense.
So I have to use a timer to stall my application for one second.. But seriously, that doesn't make sense to me. There has to be a better way, no?
I can provide more code if anyone's willing to help me out with this. Thanks.
It seems you didn't completely understand my answer to your other question.
//Note the argument should NOT be a Mouse event, because that would be dispatched by a View.
//you should not have this kind of business logic in a View
protected function loginUser(event:UserEvent):void {
//validate before even trying to create a user, since this
//has the side effect of running an HTTPService call
if (event.login != null && event.password != null) {
user = new User(event.email, event.password);
//these are plain old events dispatched by your user
//Note that strictly speaking a user should not be retrieving itself
user.addEventListener('loginSuccess', onLoginSuccess);
user.addEventListener('loginFailed', onLoginFailed);
} else {
dispatchEvent(new Event('incompleteUserDetails'));
}
}
protected function onLoginSuccess(e:Event):void {
//do your thing here
}
protected function onLoginFailed(e:Event):void {
//trigger your error handling logic here
}
Your UserEvent Class should look something like this:
package service.event {
public Class UserEvent extends Event {
public static const REQUEST_LOGIN:String = 'requestLogin';
//you may have other events that apply to users as well
public var email:String;
public var password:String;
public function UserEvent (type:String, email:String, password:String):void {
//I assume this will be dispatched from a View, given your existing code, so it will bubble by default
super(tyoe, true, true);
this.email=email;
this.password=password;
}
override public function clone():Event {
return new UserEvent(type, email, password);
}
}
}
You would dispatch that from the View based on the values of your email and password fields and listen for it higher up on the display list.
The correct pattern to use is a call back or an event listener on your User object.
protected function loginUser(event:MouseEvent):void
{
...
user = new User(txtEmail.text, txtPassword.text);
// pass a callback function to the login method
user.login(user, function(result:Boolean):void{
// do something with the result
});
}
You'll have to excuse my ActionScript I haven't done any in a little while. But hopefully you get the idea. You should never get your self into a position of setting a timer to poll a boolean on an object in Flex, the answer is always to register a listener or some description.
Incase you are using HttpService object , I think the right way to go around is to use the "ResultEvent" and the "FaultEvent" on this HttpService object to process the data, after it has arrived.
You can see the following examples on how to use the HttpService object so that , it triggers a function after the data has been completely received from the server.
http://help.adobe.com/en_US/Flex/4.0/AccessingData/WS2db454920e96a9e51e63e3d11c0bf69084-7fdc.html
http://help.adobe.com/en_US/Flex/4.0/AccessingData/WS2db454920e96a9e51e63e3d11c0bf69084-7fdc.html#WS2db454920e96a9e51e63e3d11c0bf66651-7ff3

AS3: Loading swf as Custom Class that extends MovieClip - getting null object reference

I followed the example from a previous question and I am loading an external swf using a loader and inside the loader event handler I am trying to cast the loader.content as my custom class PanelReferenceClip which extends MovieClip
When I publish I receive a this error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
Just to make sure and test that the swf location was correct and the swf was actually being loaded, I changed the type of the content to as MovieClip and it worked fine.
EDIT: I also wanted to add that these swfs are being stored locally and not being pulled across the internet, multiple networks or servers.
I am not sure if I did something quirky in my class so I am providing the source to my custom class PanelReferenceClip
package com.components
{
import com.UI.DevicePanel;
import flash.display.MovieClip;
/**
* ...
*
* used to store the loaded swf inside the panel
*
* *parentPanel is set so that it is able to access it's parent Panel when needing
* to set parameters.
*/
public class PanelReferenceClip extends MovieClip
{
private var _parentPanel:DevicePanel;
private var _bg_mc:MovieClip;
private var _oldY:Number = 0;
private var _oldX:Number = 0;
private var _IsDragging:Boolean = false;
public function PanelReferenceClip() {
super();
}
/*--------------------------------------------------------------------------
* GETTERS AND SETTERS
* -----------------------------------------------------------------------*/
public function set parentPanel(p:DevicePanel):void {
_parentPanel = p;
}
public function get parentPanel():DevicePanel {
return _parentPanel;
}
public function get bg_mc():MovieClip {
try {
return getChildByName("bg_mc") as MovieClip;
} catch (e:Error) {
trace("could not find bg_mc in " + _parentPanel.DeviceName + " panel");
}
return null;
}
public function set oldY(n:Number):void {
_oldY = n;
}
public function get oldY():Number {
return _oldY;
}
public function set oldX(n:Number):void {
_oldX = n;
}
public function get oldX():Number {
return _oldX;
}
public function set IsDragging(b:Boolean):void {
_IsDragging = b;
}
public function get IsDragging():Boolean {
return _IsDragging;
}
}
}
Here is the part of another class that is loading the swfs and then trying to assign them as the class prop _reference which is of type PanelReferenceClip . I am doing this so I am able to get ahold of the swf and it's children because when you import a swf you do not get to set the instance name of the imported swf. So I am assigning it a custom class that extends MovieClip so I can store have some custom properties.
private function handleLoad(e:Event):void
{
e.target.removeEventListener(Event.COMPLETE, handleLoad, false);
// keep reference to the content
_reference = e.target.content as PanelReferenceClip;
// ** BREAKS ON THE NEXT LINE **/
trace(_reference.numChildren);
// add loader to the display list so we can see the external SWF.
addChild(e.target.loader);
// signal the sim engine that the swf has loaded
// and to go ahead and wire up the components
dispatchEvent(new DataEvent(DataEvent.COMPLETE));
initPanel();
}
Here is the method used to load the swf. I added in the application context part to try it out but I am still not getting anywhere.
public function loadSWF(theSWF:String):void
{
var url:String = theSWF;
var urlReq:URLRequest = new URLRequest(url);
_urlError = url;
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoad);
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
_loader.load(urlReq,context);
_loader.mouseEnabled = false;
}
This might be caused by converting between types that can't be typecast. Instead of an error occurring when performing someVariable as OtherClass, the variable will just become null. (Example Shown)
I would store a reference to the original movieclip as one of the properties in PanelReferenceClip, and just use that reference when you need to access things like .numChildren
//MovieClips are subclasses of Sprites... this conversion works
var originalMC:MovieClip = new MovieClip();
var sprite:Sprite = originalMC as Sprite;
trace("sprite: " + sprite); //defined
//Sprites are not subclasses of MovieClips... this conversion wont work
var originalSprite:Sprite = new Sprite();
var mc:MovieClip = originalSprite as MovieClip;
trace("mc: " + mc); //null
//MovieClips are not subclasses of PanelReferenceClips (quite the opposite)
//this conversion wont work, just as the one before
var panelRef:PanelReferenceClip = originalMC as PanelReferenceClip;
trace("panelRef: " + panelRef); //null
As mentioned by #nox-noctis, the problem may be due to the applicationDomain. I answered a similar question here.
The underlying cause of the problem is that there are actually two different classes defined for PanelReferenceClip. Even though they have they may have same name, and contain the same code, Flash sees them as two different objects.
If you give the classes two different names, this will more clearly convey how the classes appear to Flash. In essence you are telling Flash to cast one object type to a different type, eg:
var foo:Foo = new Foo();
var bar:Bar = foo as Bar(); // where Bar does not inherit Foo
What would work though, is to case the panel as a MovieClip, since the panel does extend MovieClip. That would let you add it to the display list, but won't provide an API to the panel's implementation.
One solution is to specify an interface for the classes. This tells Flash that even though the code may be different, the two classes can be used in the same way. This also has the advantage that you only need to compile the interface into the parent SWF, and not the whole class, reducing the file size of the parent.
An interface for the panel might look like this:
public interface IPanelReference
{
function set parentPanel(p:IDevicePanel):void;
function get parentPanel():IDevicePanel;
function get bg_mc():MovieClip;
function set oldY(n:Number):void;
function get oldY():Number;
function set oldX(n:Number):void;
function get oldX():Number;
function set IsDragging(b:Boolean):void;
function get IsDragging():Boolean;
}
Apply the interface to the panel like this:
public class PanelReferenceClip extends MovieClip implements IPanelReference
{
...
}
The parent would then reference the loaded class by the interface and known ancestors:
private function handleLoad(e:Event):void
{
...
_reference = e.target.content as IPanelReference;
trace((_reference as DisplayObjectContainer).numChildren);
trace(_reference.oldX);
addChild(_reference as DisplayObject);
....
}
The facts provided are not conclusive, as it is very important where is the loader SWF and from where does it load external SWFs.
My guess: your loaded content resides in a different ApplicationDomain. See LoaderContext.applicationDomain and second parameter to Loader.load() method.
Some details on the mechanics. Apparently, you compile PanelReferenceClip class into two different SWF files. When you load some external SWF file with code in it, VM decides whether to mix or not any incoming declarations with those of loader SWF depending on loader context you provide. If the context you specified allows mixing, then incoming SWF uses the same declarations with coinciding qualified names that parent SWF has. If not -- the classes being initialized are different even if their fully qualified names are identical. In the latter case you will not be able to cast loaded content to what you like.
Try the following tests in handleLoad() method:
trace(getQualifiedClassName(e.target.content)); // This is the incoming declaration
trace(getQualifiedClassName(PanelReferenceClip)); // This is the parent declaration
Even if the output is identical, that doesn't necessarily mean, that the classes are the same. If you are using some kind of debugger, you may look at the memory addresses in the following test lines.
var incomingClass:Class = e.target.content["constructor"];
var residentClass:Class = PanelReferenceClip;
trace(incomingClass == residentClass); // toggle breakpoint here and compare memory addresses

AS3 driving me nuts

Ok here is what I am currently trying to do. I have a class called vdata.as which takes 2 paramaters both are strings sent from the main stage. Parameter one is the location for an XML file that I need to open and read. The second parameter is the name of the video I am currently looking for.
Now I can get the data from the XML file and display it with out any issue if its called from my class but when I try to access any of it from the stage I get undefined.
import flash.net.*;
import flash.display.*;
import flash.events.*;
public class videoData
{
private var mName:String;
private var mLink:String;
private var mCategory:String;
public static var elementArray:Array;
// Constructor
public function videoData(xmlPath:String,xmlVidSrc:String,pMC:MovieClip)
{
pXmlPath = xmlPath;
pXmlVidSrc = xmlVidSrc;
xmlloader = new URLLoader();
elementArray = new Array();
}
public function getXML()
{
XMLData();
}
private function XMLData()
{
xmlloader.load(new URLRequest(pXmlPath));
xmlloader.addEventListener(Event.COMPLETE,parseXMLData);
}
private function parseXMLData():void
{
var x:XML = new XML(xmlloader.data);
Init(x);
}
private function Init(m:XML):*
{
var i:Number;
for(i=0; i<m.videos.videoname.length(); i++)
{
if(m.videos.videoname[i].#name == pXmlVidSrc)
{
videoData.elementArray.push(m.videos.videoname[i].#name);
videoData.elementArray.push(m.videos.videoname[i].#category);
videoData.elementArray.push(m.videos.videoname[i].link.#url);
}
}
}
}
When I call it from the main stage the code is as follows.
var xData:videoData = new videoData(xmlPath,vidSrc,this);
xData.getXML();
then when I try to access any elements of videoData.elementArray they come up undefined...
Im just smacking my head on my desk trying to figure this out any help would be great.
Why is elementArray a static var, you only need to make it public to use it outside the function.
I'm quite confusing but you may want to try a debugging tool like "De MonsterDebugger", I would start by tracing xmlloader.data in the parseXMLData function.
"addEventListener" doesn't "fire"...the event does. You'll need to add a boolean to state for the stage that elementArray has been populated and set that after the init function.
Is elementArray something that needs to be true across all instances of videoData? If not, it shouldn't be static. You can use MovieClip(this.root).xData to access that instance of the video class from one of your other classes.
If the event has completed and the array is still empty - then it wasn't populated by your parser. You can also do checks to see if the elementArray.length > 0.
EDIT in response to comment:
as a public member or preferably a read-only property make a boolean variable:
var parseComplete:Boolean;
Set it to false in your constructor.
Then, after your call to "Init" in your Event.COMPLETE callback set:
parseComplete=true;
Then make sure parseComplete == true before you ever access elementArray. If you're waiting for the parser to complete you might want to set a timeout or some sort of try/catch mechanism just in case there are any unforeseen errors that would cause some sort of:
while( !xData.parseComplete ) { }
To loop indefinitely. It all depends on the usage. Personally I'd probably add a callback from the event listener to the stage to trigger whatever is supposed to happen next.

Creating a custom trace() class in AS3

I got this idea of expanding my trace() messages.
Why
trace() is all over my code, I want to turn them on/off by a simple command and maybe add some sort of priority functionality to the trace(), i.e.
myTrace.TraceMsg("loosehere",debugme, 0);
myTrace.TraceMsg("winhere",debugme, 1);
And when I run, only the one with the higher priority, "1" in this case, shows.
There is a lot more functionality I would like to add as well, like logging messages to file and so on.
Problem
How do trace() work? -Is it possible to overload trace() somehow? -How would I implement the custom TraceMsg(what code here?) method?
Having some serious problems finding info on this subject on our favourite search engine, so any help would be appreciated.
I have come up with a rather efficient, yet tedious way of using my own trace() function in Flash only projects, but calling it simply with
trace("this", "that", "and that too");
I basically implement one trace() method in every class of my project, that calls a public function (so that i can call the real trace() function from there.
here is what I do : in every class I call this
include "trace_implementation.as";
in the .as file comes a simple method implementation (it could be a static method too).
public function trace(... arguments){
for(var i in arguments){
myTrace(arguments[i]);
}
}
and the myTrace function is defined in its own myTrace.as file
package pt.utils{
import flash.external.ExternalInterface
public function myTrace(_s:String):void{
trace(_s);// this will call the original flash trace() function
ExternalInterface.call("console.log", _s);// to get traces outside of flash IDE
/*implement what you want here*/
}
}
so now when I compile with "omit trace actions", my whole debugging is ignored as if I used trace() simply.
the really good part here is that you could implement custom actions depending on instructions you give in the trace, so :
trace(Debug.DEBUG_MESSAGE, "message to output in debug");
trace(Profile.START_PROFILING, this, 'name');
/*do heavy code*/
trace(Profile.STOP_PROFILING, this);
then dispatch it from myTrace, or a Tracer class or anything :)
Hope this helps future tracers.
trace() itself is a top-level function, not a class, so unfortunately we cannot extend it. That being said, we can utilize it in a simple class to do just what it does normally, only in this case the trace is based on conditions (i.e. Boolean - true|false, etc). First we create the Trace class, which we wouldn't instantiate ourselves because we are utilizing a Factory design pattern through the class below, Tracer. Tracer is built around the singleton design pattern, yet utilizes the Factory pattern to instantiate instances of Trace, when the trace method of Tracer is called.
//This class is handled by Tracer, which is right below it.
//You WILL NOT instantiate these, nor hold references.
package
{
public class Trace
{
private function _value:*;
private function _trace:Boolean;
public function Trace(pValue:*, pTrace:Boolean):void
{
_value = pValue;
_trace = pTrace;
}
public function get value():*
{
return _value;
}
public function get trace():Boolean
{
return _trace;
}
}
}
//This is the important class and the only one you will work with.
package
{
/**
*Utilizes Singleton and Factory design patterns.
*/
public class Tracer
{
private var _traceArray:Array;
private static var _instance:Tracer;
public function Tracer(pvt:PrivateClass = null):void
{
if(pvt == null)
{
throw(new Error("You cannot instantiate this class directly, please use the static getInstance method."));
}
_init();
}
public static function getInstance():Tracer
{
if(Tracer._instance == null)
{
Tracer._instance = new Tracer(new PrivateClass());
}
return Tracer._instance;
}
public function trace(pValue:*, pTrace:Boolean):void
{
var trace:Trace = new Trace(pValue, pTrace);
if(trace.pTrace)
{
trace(pValue);
}
}
//Since we have the option for individual traces to be disabled
//I provide this to get access to any and all later.
public function traceAll():void
{
traceStr:String = _traceArray.toString();
}
public function get traceables():Array
{
return _traceArray;
}
//Here we provide a method to trace all, even if set to false in their constructor.
private function _init():void
{
_traceArray = new Array();
}
}
}
//Here we create a class that is OUTSIDE of the package.
//It can only be accessed from within this class file. We use this
//to make sure this class isn't instantiated directly.
class PrivateClass
{
function PrivateClass():void
{
trace('can only be accessed from within this class file');
}
}
//Now for use in doc class
package
{
import flash.display.Sprite;
import flash.events.Event;
//No need to import Tracer and Trace, they are also in the
//unnamed package.
public class DocumentClass extends Sprite
{
private var _tracer:Tracer;
public function DocumentClass():void
{
if(stage) _init();
else addEventListener(Event.ADDED_TO_STAGE, _init);
}
private function _init(e:Event = null):void
{
_tracer = Tracer.getInstance();
_tracer.trace(10*20, false);
_tracer.trace(10*20, 0); //SAME AS ABOVE
_tracer.trace("I love AS3", true); //traces
_tracer.traceAll(); //Would trace: 200, 200, I love AS3
}
}
}
Keep in mind this is off the hip and very well could have a bug or two, but the idea is there; That is to say that this is not tested, it is merely to give you an idea of how you might implement this.
I hope this helps.
Look at the Flex logging API, particularly the section: Implementing a custom logger with the logging API.
Look up the TraceTarget class as well.
You can't override trace itself, but for ease of typing I like to create a global function called 'tr'. It's a little known fact that you can create global functions in AS3, but it's easy.
Create a file called tr.as inside you main source directory (not in a subdirectory or package), with the contents:
package {
public function tr(msg:String, ...):void {
// add custom trace logic here
trace("tr message: "+msg);
}
}
If you need to have a lot of logic or static storage variables etc, it might be better to make a separate static class, and have the global tr function call out to that, such as:
package {
import org.code.MyTracer;
public function tr(msg:String, ...):void {
MyTracer.tr(msg); // all the tracing logic is inside the MyTracer class
}
}
Here is a super simple custom trace function I use. debugFlag can be set to true/false
elsewhere in the package.
public static function myTrace(... vars) :void {
if (debugFlag) {
var output:Array = new Array;
for each (var arg in vars) {
output.push(arg);
}
trace(output);
}
}
In AS2, it was possible to override the global trace function by doing something like this (taken from memory, might be a bit wrong but the gist of it is there):
public static var realTrace:Function = _global["trace"];
// This is put in some init code somewhere
_global["trace"] = myTrace;
public static function myTrace(... args):void
{
// Do whatever you want with args here, build a nice formatted string or whatever
// before passing to realTrace. Using with MTASC one could add line numbers, class
// names and all sorts of nice meta data. Or just return should you want to turn
// tracing off.
realTrace.apply(args);
}
Unfortunately I haven't found a way to do the same in AS3. Yet.
Trace is a top-level function, so you can't override it, and as far as I know, it does not fire any events. Since it's a top-level function (not contained in any named package), you can use it without import statements.
Here is an example of a top-level "Tracer" class that you can use in place of trace without import statements.
Just call "Tracer.write" or "Tracer.writeError" for tracing Error objects.
"Tracer.write" accepts a variable number of arguments, just like the built-in trace function. "Tracer.writeError" is a helper method that allows you to easily trace Error objects.
Features:
Calls built-in trace.
Keeps a log of all your calls to Tracer.write as an array of strings.
The call log is accessible as a string through getText, which joins all elements in the array with a newline character and will optionally tack on line numbers!
Fires events when new lines are added to the log, so if you have some kind of display window for the log, the display window can listen for Tracer events to update the log display in real-time as the events occur. This is great for displaying trace events when running inside a web browser or stand-alone player.
-Tracer class definition
package
{
import flash.events.EventDispatcher;
public class Tracer extends EventDispatcher
{
private static var traced_text:Array = new Array( "--Start of Trace Log--" );
public static var enabled:Boolean = true;
private static var suspended:Boolean = false;
public static var instance:Tracer = new Tracer();
public static const newline:String = "\n"; //workaround for TextField.appendText bug.. use "\n" instead of "\r". See note and link to bug post in getText method
public function Tracer()
{
}
static public function write( ...args ):void
{
if (enabled && !suspended)
{
trace.apply( null, args );
var text:String = args.join( newline );
var next_index:int = traced_text.length;
traced_text.push( text );
suspended = true; //prevent recursive calls from TracerEvent handler
instance.dispatchEvent( new TracerEvent( text, next_index ) );
suspended = false;
}
}
static public function writeError( e:Error ):void
{
write( "errorID: " + e.errorID, "errorName: " + e.name, "errorMessage: " + e.message, "stackTrace: " + e.getStackTrace() );
}
static public function getText( include_line_numbers:Boolean ):String
{
var line_count:int = traced_text.length;
var lines:Array = traced_text; //store pointer to traced_text; pointer may be changed to reference an altered array that includes line numbers
if (include_line_numbers) //create temporary trace log copy with altered lines; allows quick call to join at end
{
var new_lines:Array = new Array();
for (var i:int = 0; i < line_count; i++)
new_lines.push( i.toString() + ": " + lines[i] );
lines = new_lines;
}
return lines.join( newline ); //do not include last newline character (workaround for bug in appendText method (https://bugs.adobe.com/jira/browse/FP-1982); I have to call appendText with newline character first, otherwise it has issues like not acknoledging the newline thats already there at the end).
}
static public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
{
instance.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
static public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
{
instance.removeEventListener(type, listener, useCapture);
}
static public function willTrigger(type:String):Boolean
{
return instance.willTrigger(type);
}
static public function hasEventListener(type:String):Boolean
{
return instance.hasEventListener(type);
}
}
}
-TracerEvent class definition
package
{
import flash.events.Event;
public class TracerEvent extends Event
{
public static const WRITE:String = "te_write";
public var text:String;
public var index:int; //index of newly traced text in the traced_text array (trace log)
public function TracerEvent( text:String, index:int )
{
super( WRITE, false, false );
this.text = text;
this.index = index;
}
override public function clone():Event
{
return new TracerEvent( text, index );
}
}
}
As mentioned below, there is no way to override trace (at least not if you want your traces to reach the output stream), but it's actually very easy to create your own universally accessable logging function. Plus, you can even define a universally accessable boolean to turn logging on or off:
log.as (note that the filename must reflect the name of the function)
package {
function log(... arguments):void {
trace("Custom logging FTW!");
if (logEnabled)
trace(arguments);
}
}
logEnabled.as (note that the filename must reflect the name of the variable)
package {
var logEnabled:Boolean = true;
}
Main.as
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
log("Testing");
logEnabled = false;
log("Testing2");
}
}
}
Response
Custom logging FTW!
Testing
Custom logging FTW!
you dont need to override it , just create a function in your project and call it trace then any trace call will point to this.trace ;)
function trace(... arguments){
yourfunction(arguments);
}