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
No comments:
Post a Comment