Worker not receiving messages after RUNNING state, but it does later - actionscript-3

Update: Apparently, it's some kind of timing issue. In the code below I create the worker from a click handler, but if I create the worker from the Main constructor (as the doc example does), the commandChannel message gets to the worker -- even though in either case I'm doing the exact same thing: create the worker, listen for RUNNING state, then send message over commandChannel. But that first message only gets through if I create the worker from the Main constructor, while messages later in time (some undetermined timed after RUNNING) always get through. Why? If I can't rely on WorkerState.RUNNING to know when I can send messages to the worker, how can I know when the worker is ready to receive messages?
I basically have the exact same thing as the documentation example, but it's not working. My setup is:
Flash Pro CC
AIR 17 for Desktop
Windows 7
The problem is that the worker does not seem to receive messages through the incoming message channel, though it can successfully send messages to the main SWF through an outgoing message channel.
This is my main class:
public class Main extends MovieClip {
[Embed(source="TestWorker.swf", mimeType="application/octet-stream")]
private var WorkerSWF:Class;
private var workerBytes:ByteArray;
private var worker:Worker;
private var commandChannel:MessageChannel;
private var resultChannel:MessageChannel;
public function Main() {
workerBytes = new WorkerSWF();
stage.addEventListener(MouseEvent.CLICK, clickHandler);
}
private function clickHandler(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.CLICK, clickHandler);
startWorker();
}
private function startWorker():void {
trace("Start worker");
worker = WorkerDomain.current.createWorker(workerBytes, true);
commandChannel = Worker.current.createMessageChannel(worker);
worker.setSharedProperty("commands", commandChannel);
resultChannel = worker.createMessageChannel(Worker.current);
resultChannel.addEventListener(Event.CHANNEL_MESSAGE, resultMessageHandler);
worker.setSharedProperty("results", resultChannel);
worker.addEventListener(Event.WORKER_STATE, workerStateHandler);
worker.start();
}
private function workerStateHandler(e:Event):void {
trace("Worker state:", worker.state);
if(worker.state == WorkerState.RUNNING) {
trace("Worker running");
commandChannel.send("START PLEASE");
}
}
private function resultMessageHandler(e:Event):void {
trace(e, resultChannel.receive())
}
}
And my worker SWF class:
public class TestWorker extends Sprite {
private var commandChannel:MessageChannel;
private var resultChannel:MessageChannel;
public function TestWorker () {
trace("isPrimordial:", Worker.current.isPrimordial)
commandChannel = Worker.current.getSharedProperty("commands") as MessageChannel;
commandChannel.addEventListener(Event.CHANNEL_MESSAGE, commandMessageHandler);
resultChannel = Worker.current.getSharedProperty("results") as MessageChannel;
resultChannel.send("Hello from worker");
}
private function commandMessageHandler(e:Event):void {
trace("commandMessageHandler")
//var message:String = commandChannel.receive();
resultChannel.send("Whatever.");
setInterval(respond, 1000);
}
private function respond(){
resultChannel.send("Whatever " + new Date());
}
}
The problem is that the worker's commandMessageHandler is never triggered. I know this because I never receive the "Whatever" message back to the Main SWF. The "Hello from worker" message does get back to the Main SWF, so it's something specific to the main-to-worker message channel. What's wrong?
Additionally, it seems that neither trace actions or runtime errors from the worker are captured by the main SWF. How does one debug a worker SWF?

This seems to be a known issue (see point 2). WorkerState.RUNNING sometimes gets dispatched before the constructor of the worker has actually run. So in my case, from the click handler, the first message got sent before the constructor of the worker had added the listener to receive it.
So I'm going with the simple workaround: when the worker loads, send a message to the main thread that says "I'm ready" at the end of the constructor. In the main thread, listen for that instead of RUNNING before sending the intial command.

Related

AS3 - Local P2P networking receiving only "undefined" data

I'm trying to make a simple P2P networking system to send messages to other Flash Players on your network.
However, when I send a message the receiving player doesn't receive a message but rather a undefined object.
I did allow Flash Player in my Virus Scanner (NOD 32) and my Windows Firewall by the way.
Here's my P2P class:
package com.snakybo.as3framework.network {
import flash.events.Event;
import flash.events.NetStatusEvent;
import flash.net.GroupSpecifier;
import flash.net.NetConnection;
import flash.net.NetGroup;
/** #author Kevin */
public class P2P extends NetConnection {
private var netCon:NetConnection;
private var netGroup:NetGroup;
private var handler:Function;
private var groupName:String;
public function P2P(groupName:String, handler:Function) {
this.groupName = groupName;
this.handler = handler;
netCon = new NetConnection();
netCon.addEventListener(NetStatusEvent.NET_STATUS, conHandler);
netCon.connect("rtmfp:");
}
/** Post a message */
public function post(txt:*):void {
var data:Object = new Object();
data["txt"] = txt;
data["id"] = new Date().time;
netGroup.post(data);
}
/** Handle connection event listener */
private function conHandler(e:NetStatusEvent):void {
if(e.info["code"] == "NetConnection.Connect.Success") {
setupGroup();
}
}
/** Connect to group */
private function setupGroup():void {
var groupSpec:GroupSpecifier = new GroupSpecifier(groupName);
groupSpec.postingEnabled = true;
groupSpec.ipMulticastMemberUpdatesEnabled = true;
groupSpec.addIPMulticastAddress("225.225.0.1:30000");
netGroup = new NetGroup(netCon, groupSpec.groupspecWithAuthorizations());
netGroup.addEventListener(NetStatusEvent.NET_STATUS, handler);
dispatchEvent(new Event(Event.COMPLETE));
}
}
}
Connection seems to be working, since I can send messages and the other player does receive them.. well.. receive something.
Then from my Main.as I call this class like this:
private var p2p:P2P;
public function Main():void {
p2p = new P2P("snakybo", netHandler);
p2p.addEventListener(Event.COMPLETE, onConnected);
}
private function onConnected(e:Event):void {
function send(e:KeyboardEvent):void {
c.post("a");
trace("poseted");
}
stage.addEventListener(KeyboardEvent.KEY_DOWN, send);
}
private function netHandler(e:NetStatusEvent):void {
switch(e.info["code"]) {
case "NetGroup.Posting.Notify":
trace("received: " + e.info["txt"]);
break;
}
}
Also, if I add:
case "NetGroup.Connection.Success":
trace("Connected to group");
break;
It never prints that. Not sure what's wrong with that.
Am I doing anything wrong here? Or did my PC somehow block Flash Player?
In your P2P class you have this...
netCon.connect("rtmfp:");
This tells your NetConnection to connect to a string ("rtmfp:") which isn't a server or valid connection type if you are trying to reach outside members of the rtmfp connection. It appears you are using this for an internal network so "rtmfp" will work locally.
What I do with NetConnection.connect() is I set up my rtmfp constant and a developer key constant (for cirrus server) right off the bat like this...
cons SERVER:String = "rtmfp:";
cons DEVKEY:String = "your developer key here";
then
function connect(): void {
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
nc.connect(SERVER + DEVKEY);
}
You can go to adobes website if you are using their rtmfp server to get a developer key, they are free and easy to get anyway. Once you are connected you will get a NetStatus event string of "NetConnection.Connect.Success" which your conHandler function is set up to receive. I use a Switch Case within mine to listen for the connect string and do something based off each string like this:
function netStatus(event: NetStatusEvent): void {
switch (event.info.code) {
case "NetConnection.Connect.Success":
setupGroup();
break;
} }
Fire your setupGroup() function when you hear "NetConnection.Connect.Success". Then within your setupGroup() go ahead and set your
groupspec.serverChannelEnabled = true;
groupspec.routingEnabled = true;
groupspec.objectReplicationEnabled = true; // and I have
groupspec.peerToPeerDisabled = false;
Also, in your setupGroup you are firing "handler" instead of "conHandler" which is the name of your NetStatus event function handler. Probably just a typo. Setting the new NetGroup itself will fire 1 of 3 NetStatus events telling you the status of the new netgroup connection so you won't need to dispatch the event you are using unless you want to, up to you. You can handle those in your conHandler function as well.
I will typically have a trace statement setup to print out the NetStatusEvent so in your case that would be... trace(e.info.code) and you will be able to see the events as they happen and be able to troubleshoot problems much easier. Once you get this connection up you'll be able to handle the delivering of objects.
This should at least get you a created instance and the connection to others in the group. I have included a couple links I referenced here:
NetGroup
NetStatus
NetConnection
Try googling Tom Krcha, he is with adobe and has some good info on this as well.

how to return value from service result handler in flex?

I going to call only "clickfun()" method i need urlPath value after success service return value. but it throws stack overflow errors. Any help please.
private function clickfun(eve:Event):String{
languagecode = "assets/myFont_en.swf";
Defaultlanguagecode = "assets/myFont_default.swf";
var request:URLRequest = new URLRequest(languagecode);
var xmlURLLoader:URLLoader = new URLLoader(request);
xmlURLLoader.addEventListener(Event.COMPLETE,loadcompletefun);
xmlURLLoader.addEventListener(IOErrorEvent.IO_ERROR,ioerrorFun);
xmlURLLoader.load(request);
return getpath();
}
private function getpath():String{
if(loadcomplete == true){
Alert.show(urlpath);
return urlpath;
}else
return getpath();
}
private function loadcompletefun(e:Event):void{
loadcomplete = true;
urlpath = languagecode;
}
private function ioerrorFun(e:IOErrorEvent):void{
loadcomplete = true;
urlpath = Defaultlanguagecode;
}
<mx:Panel title="Embedded Fonts Using ActionScript" width="800" height="500">
<mx:Button label="Click" id="btn" click="clickfun(event)"/>
</mx:Panel>
The obvious with your remote interaction code is that loaders load data asynchronously. This means that the execution of the program continues while the loaders load data on a (virtually) different thread.
The issue is here you call getpath() right after you have started the load. This makes loadcomplete false, and the getpath function keeps recursing and the stack overflows.
What you SHOULD so is:
Let your class dispatch an event. Say you dispatch just a Event.COMPLETE event.
Tell it to the IDE like this:
Near your class declaration, add the metadata and make your class extend EventDispatcher
//Imports and package declaration
[Event(name="complete", type="flash.events.Event")]
public class YourClassName extends EventDispatcher {
//Remaining part of class here
Then, in your loadcompletefun, add this
dispatchEvent(new Event(Event.COMPLETE));
And, in the place you call clickfun, do this:
o=new YourClassName();
o.addEventListener(Event.COMPLETE, gp);
and, declare gp as
private function gp(e:Event):void {
trace(getpath());
//You now have the ability to call getpath()
}
Based on a review of the code, I'm guessing you are receiving an error because the loadcomplete and urlpath variables are not defined. so, make sure you define it in your ActionScript block:
public var loadcomplete : Boolean
public var urlPath : String
Also make sure that you define languagecode and Defaultlanguagecode as variables using the same approach.
Any one of these omissions could cause a compile time or runtime error.

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

Asynchronous Lazy Loading in ActionScript

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.

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.