Well, after much deliberation, the solution was to stick with an external list manager to control Server Queries. Adding the list management to the ServerQuery class module proved to be more trouble than it was worth since each query would need to track its own status and timing. This could be better served with an external management class.
All that was needed to support an external manager was a simple callback function to allow the manager to know when queries finished execution.
Here is the pre-test code for the ServerQuery and ServerQueryList classes:
import flash.events.*;
import flash.net.*;
//
// a static helper class to queue queries to the server, all in one place... reports average server response time as a bonus!
//
public class ServerQueryList {
static var serverQs : Array = new Array(); // array of ServerQuery objects
static var _networkLatency : int = 0; // the average response time from the server
function ServerQueryList() {
}
// request data from the server... takes URLstring and callback function as paramaters
static function QueryServer(req:String, callback:Function, bMeasureLatency:Boolean=false) {
// create a new ServerQuery object
var sq : ServerQuery = new ServerQuery(req, callback);
// set the callback to our private function
sq.setListCallback(sqCallback);
// push it onto our list
newindex = serverQs.push(sq);
// pass along the index... push actually adds to end of array not to beginning ala a stack... confusing function name there
sq.setIndex(newindex);
sq.Load(); // do it!
}
private function sqCallBack(target:ServerQuery) {
// our target has returned...
// record the round trip time and then delete it from the array
var sqIndex = target.getIndex();
if (sqIndex != -1 && sqIndex < serverQs.length) { // validate index
accumulateLatency(ServerQuery(serverQs[sqIndex]).getResponseTime());
//do the cleanup!
ServerQuery(serverQs[sqIndex]).kill();
delete serverQs[sqIndex];
serverQs[sqIndex] = null;
serverQs.splice(sqIndex, 1);
}
}
private function accumulateLatency(newResponseTime:int) {
// add the response time to our aggregate response time
if (_networkLatency != 0)
_networkLatency = (_networkLatency + newResponseTime) / 2;
else
_networkLatency = newResponseTime;
}
} // end ServerQueryList
import flash.events.*;
import flash.net.*;
//
// actually gunna leave this one as a generic query class.. will make a queue class to manage these
// a helper class to queue queries to the server, all in one place... reports average server response time as a bonus!
//
public class ServerQuery {
private var _urlloader : URLLoader;
private var _callback : Function; // supplied by caller
private var _urlrequest : URLRequest;
private var _listCallback : Function; // supplied by query list manager
private var _responseTime : int; // milliseconds for round trip
private var _opened : Number; // time when connection opened
private var _closed : Number; // time when response has arrived
//TODO: add timer for timeout failure
private var _debugStr : String;
private var _index : int; // the index of this query within the external list of queries
public function ServerQuery(req:String, callback:Function) {
_debugStr = "";
_urlloader = new URLLoader();
configureListeners(_urlloader);
_urlrequest = new URLRequest(req);
_callback = callback;
_urlloader.addEventListener(Event.COMPLETE, CTQueryPreCallback);
_responseTime = -1;
_index = -1;
_listCallback = null;
}
public function setListCallback(newFunc:Function) {
_listCallback = newFunc;
}
public function setIndex(newIndex:int) {
_index = newIndex;
}
public function getIndex() : int {
return _index;
}
public function getResponseTime() : int {
return _responseTime;
}
public function Load() {
try {
var date : Date = new Date();
_opened = date.getTime();
_urlloader.load(_urlrequest);
} catch (error:Error) {
_debugStr += "ServerQuery: Unable to load requested document";
}
}
public function getDebugReport() : String {
return _debugStr;
}
public function kill() {
// do some cleanup
delete _urlloader;
delete _urlrequest;
}
private function CTQueryPreCallback(e:Event) {
deconfigureListeners(_urlloader);
var date : Date = new Date();
_closed = date.getTime();
_responseTime = _closed - _opened;
// inform the list manager, if any, we have returned.. pass it our this pointer
if (_listCallback != null)
_listCallback(this);
if (_callback != null)
_callback(e); // call our stored callback
}
private function configureListeners(dispatcher:IEventDispatcher):void {
//dispatcher.addEventListener(Event.COMPLETE, completeHandler);
//dispatcher.addEventListener(Event.OPEN, openHandler);
//dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
private function deconfigureListeners(dispatcher:IEventDispatcher) {
dispatcher.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
dispatcher.removeEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
dispatcher.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
private function securityErrorHandler(event:SecurityErrorEvent):void {
trace("ServerQuery: securityErrorHandler: " + event);
_debugStr += "securityErrorHandler: " + event + "\n";
}
private function httpStatusHandler(event:HTTPStatusEvent):void {
trace("ServerQuery: httpStatusHandler: " + event);
_debugStr += "httpStatusHandler: " + event + "\n";
}
private function ioErrorHandler(event:IOErrorEvent):void {
trace("ServerQuery: ioErrorHandler: " + event);
_debugStr += "ioErrorHandler: " + event + "\n";
}
} // end class

Tuesday, September 27, 2011
Cohesive vs Coupled Server Query List in AS3
Well an esteemed colleague of mine suggested I should make a "bad ass" blog about my trials and tribulations in software development. ;)
I am a sole proprietor in the games industry, off and on for 18+ years. My latest passion is Flash and Actionscript programming. In short, the Flash IDE and Actionscript largely "think like I do" where as previously in my career I have frequently struggled with the IDE and the "gotchas" I've associated with them.
Well without further adieu, today I'm developing an Actionscript class to handle server queries for my up and coming Facebook game codenamed "PvP". During the development of Cruise Time I experimented with various ways of handling server queries as cohesively as possible but was frustrated by a lack of time to get it really "right."
PvP will require some pseudo-real-time elements in that the player will need to complete movement and firing orders within a set time limit orchestrated by a "heartbeat" on the server. Each "turn" on the server will have a precise time limit and therefore the user must be aware of this heartbeat and must thus strive to get as many orders in as possible before "time is up."
Therefore, when making server queries the inherent network latency and server response time must be measured to properly estimate when "time is up" for each user. For example, with a turn-time of 30 seconds, a user with a 5 second latency will need to complete all of his orders in 25 seconds to account for the round trip time of communication between client and server in order to keep all players on the same turn at any given moment.
But on to the coding right? Here is one of the attempts at a generic query class I made for Cruise Time:
Each CTServerQuery handles its own error checking and event cleanups. But now I would like to add the ability to queue all queries in one place and use the class as a cenral clearing house for all communication to the server. Rather than the user instancing and using individual CTServerQuery classes ad hoc, I'd like the user to simply call into a singleton and request data from the server and simply wait for the call back response with success or failure.
My first stab at this was to leave CTServerQuery alone and make a new class that would simply manage a dynamic list of CTServerQuery objects as requests came in and, as a side effect, measure the inherent network latency and server response time as well.
This came out as:
That's as far as I got before realizing that ServerQueryList would be highly coupled to CTServerQuery and would be rather removed from where the action is happening. For instance, a new callback would have to be added to CTServerQuery to inform ServerQueryList of callback activities.
So, rather than making a new highly coupled controller class, I am now thinking the list management should be a function of the CTServerQuery module and it will , itself, be a list managing and server querying module resulting in a more cohesive all-in-one approach.
Well, that's all for now. Stay tuned here to see how CTServerQuery ends up! ;)
Dracos
I am a sole proprietor in the games industry, off and on for 18+ years. My latest passion is Flash and Actionscript programming. In short, the Flash IDE and Actionscript largely "think like I do" where as previously in my career I have frequently struggled with the IDE and the "gotchas" I've associated with them.
Well without further adieu, today I'm developing an Actionscript class to handle server queries for my up and coming Facebook game codenamed "PvP". During the development of Cruise Time I experimented with various ways of handling server queries as cohesively as possible but was frustrated by a lack of time to get it really "right."
PvP will require some pseudo-real-time elements in that the player will need to complete movement and firing orders within a set time limit orchestrated by a "heartbeat" on the server. Each "turn" on the server will have a precise time limit and therefore the user must be aware of this heartbeat and must thus strive to get as many orders in as possible before "time is up."
Therefore, when making server queries the inherent network latency and server response time must be measured to properly estimate when "time is up" for each user. For example, with a turn-time of 30 seconds, a user with a 5 second latency will need to complete all of his orders in 25 seconds to account for the round trip time of communication between client and server in order to keep all players on the same turn at any given moment.
But on to the coding right? Here is one of the attempts at a generic query class I made for Cruise Time:
package
{
import flash.events.*;
import flash.net.*;
public class CTServerQuery {
private var _urlloader : URLLoader;
private var _callback : Function; // supplied by caller
private var _urlrequest : URLRequest;
private var _debugStr : String;
public function CTServerQuery(req:String, callback:Function) {
_debugStr = "";
_urlloader = new URLLoader();
configureListeners(_urlloader);
_urlrequest = new URLRequest(req);
_callback = callback;
_urlloader.addEventListener(Event.COMPLETE, CTQueryPreCallback);
}
public function Load() {
try {
_urlloader.load(_urlrequest);
} catch (error:Error) {
_debugStr += "CTServerQuery: Unable to load requested document";
}
}
public function getDebugReport() : String {
return _debugStr;
}
private function CTQueryPreCallback(e:Event) {
deconfigureListeners(_urlloader);
if (_callback != null)
_callback(e); // call our stored callback
}
private function configureListeners(dispatcher:IEventDispatcher):void {
//dispatcher.addEventListener(Event.COMPLETE, completeHandler);
//dispatcher.addEventListener(Event.OPEN, openHandler);
//dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
private function deconfigureListeners(dispatcher:IEventDispatcher) {
dispatcher.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
dispatcher.removeEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
dispatcher.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
private function securityErrorHandler(event:SecurityErrorEvent):void {
trace("securityErrorHandler: " + event);
_debugStr += "securityErrorHandler: " + event + "\n";
}
private function httpStatusHandler(event:HTTPStatusEvent):void {
trace("httpStatusHandler: " + event);
_debugStr += "httpStatusHandler: " + event + "\n";
}
private function ioErrorHandler(event:IOErrorEvent):void {
trace("ioErrorHandler: " + event);
_debugStr += "ioErrorHandler: " + event + "\n";
}
} // end class
} // end package
{
import flash.events.*;
import flash.net.*;
public class CTServerQuery {
private var _urlloader : URLLoader;
private var _callback : Function; // supplied by caller
private var _urlrequest : URLRequest;
private var _debugStr : String;
public function CTServerQuery(req:String, callback:Function) {
_debugStr = "";
_urlloader = new URLLoader();
configureListeners(_urlloader);
_urlrequest = new URLRequest(req);
_callback = callback;
_urlloader.addEventListener(Event.COMPLETE, CTQueryPreCallback);
}
public function Load() {
try {
_urlloader.load(_urlrequest);
} catch (error:Error) {
_debugStr += "CTServerQuery: Unable to load requested document";
}
}
public function getDebugReport() : String {
return _debugStr;
}
private function CTQueryPreCallback(e:Event) {
deconfigureListeners(_urlloader);
if (_callback != null)
_callback(e); // call our stored callback
}
private function configureListeners(dispatcher:IEventDispatcher):void {
//dispatcher.addEventListener(Event.COMPLETE, completeHandler);
//dispatcher.addEventListener(Event.OPEN, openHandler);
//dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
private function deconfigureListeners(dispatcher:IEventDispatcher) {
dispatcher.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
dispatcher.removeEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
dispatcher.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
private function securityErrorHandler(event:SecurityErrorEvent):void {
trace("securityErrorHandler: " + event);
_debugStr += "securityErrorHandler: " + event + "\n";
}
private function httpStatusHandler(event:HTTPStatusEvent):void {
trace("httpStatusHandler: " + event);
_debugStr += "httpStatusHandler: " + event + "\n";
}
private function ioErrorHandler(event:IOErrorEvent):void {
trace("ioErrorHandler: " + event);
_debugStr += "ioErrorHandler: " + event + "\n";
}
} // end class
} // end package
Each CTServerQuery handles its own error checking and event cleanups. But now I would like to add the ability to queue all queries in one place and use the class as a cenral clearing house for all communication to the server. Rather than the user instancing and using individual CTServerQuery classes ad hoc, I'd like the user to simply call into a singleton and request data from the server and simply wait for the call back response with success or failure.
My first stab at this was to leave CTServerQuery alone and make a new class that would simply manage a dynamic list of CTServerQuery objects as requests came in and, as a side effect, measure the inherent network latency and server response time as well.
This came out as:
package
{
import flash.events.*;
import flash.net.*;
//
// a helper class to queue queries to the server, all in one place... reports average server response time as a bonus!
//
public class ServerQueryList {
private var queries : Array; // array of ServerQuery objects
public function ServerQueryList() {
queries = new Array();
}
// request data from the server... takes URLstring and callback function as paramaters
public function QueryServer(req:String, callback:Function) {
}
} // end ServerQueryQueue
} // end package
{
import flash.events.*;
import flash.net.*;
//
// a helper class to queue queries to the server, all in one place... reports average server response time as a bonus!
//
public class ServerQueryList {
private var queries : Array; // array of ServerQuery objects
public function ServerQueryList() {
queries = new Array();
}
// request data from the server... takes URLstring and callback function as paramaters
public function QueryServer(req:String, callback:Function) {
}
} // end ServerQueryQueue
} // end package
That's as far as I got before realizing that ServerQueryList would be highly coupled to CTServerQuery and would be rather removed from where the action is happening. For instance, a new callback would have to be added to CTServerQuery to inform ServerQueryList of callback activities.
So, rather than making a new highly coupled controller class, I am now thinking the list management should be a function of the CTServerQuery module and it will , itself, be a list managing and server querying module resulting in a more cohesive all-in-one approach.
Well, that's all for now. Stay tuned here to see how CTServerQuery ends up! ;)
Dracos
Subscribe to:
Posts (Atom)