Итак, опираясь на материалы о работе с XMLSocket и возможностях отладки haXe-кода делаем собственный дебаг-сервер.
Серверная часть у нас будет на Ruby, и она будет небольшая и простая:
#!/usr/bin/ruby -w
puts 'Debug Server v 0.01'
policyInfo = '<?xml version="1.0"?>'
policyInfo += '<cross-domain-policy>'
policyInfo += '<allow-access-from domain="*" to-ports="4444,80" />'
policyInfo += "</cross-domain-policy>\0"
require 'socket'
server = TCPServer.new('localhost', 4444);
while(session = server.accept)
Thread.new(session) do |s|
s.print policyInfo
while(msg = s.gets)
msg.strip!
if msg == '<policy-file-request/>'
puts "\n =========== \n\n"
else
puts msg
end
end
end
end
Сервер просто принимает соединение, отсылает клиенту cross-domain-policy и пишит в консоль все, что присылает клиент.
Клиентская часть будет состоять из двух классов. Основной класс приложения:
/**
* @author Yzh
*/
package com.yzh44yzh.site;
import com.yzh44yzh.core.Application;
import com.yzh44yzh.core.Debug;
class ThreeManApp extends Application
{
// static
static public function main():Void { new ThreeManApp(); }
// constructor
public function new()
{
super();
this.version = '0.1';
Debug.Init(this.Start);
}
// methods
private override function Start():Void
{
trace(this + ' version: ' + version);
super.Start();
trace('Hello world!');
trace('Error: some error');
trace('And some more trace');
}
public override function toString():String { return 'ThreeManApp'; }
}
В конструкторе класса инициализируется дебаг. Ему отдается метод, который нужно вызывать, когда инициализация закончится (в данном случае это метод
Start). Дебагу нужно некоторое время, чтобы установить соединение с сервером.
А теперь сам класс дебага:
/**
* @author Yzh
*/
package com.yzh44yzh.core;
import flash.events.DataEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.XMLSocket;
import flash.system.Security;
import flash.text.TextField;
import flash.text.TextFormat;
class Debug
{
// constants
static inline var DEFAULT: String = '\033[00m';
static inline var GREY: String = '\033[01;30m';
static inline var RED: String = '\033[01;31m';
static inline var GREEN: String = '\033[00;32m';
static inline var YELLOW: String = '\033[01;33m';
static inline var BLUE: String = '\033[00;34m';
static inline var MAGENTA: String = '\033[00;35m';
static inline var CYAN: String = '\033[00;36m';
// static vars
static private var instance: Debug;
// static methods
static public function Init(ConnectHandler:Void->Void):Void
{
Debug.instance = new Debug();
Debug.instance.ConnectHandler = ConnectHandler;
Debug.instance.Connect();
haxe.Log.trace = Debug.Trace;
}
static public function Trace(data:Dynamic, ?pos:haxe.PosInfos):Void
{
var msg = data.toString();
if(Debug.instance.connected)
{
var data = BLUE;
data += pos.className + '.' + pos.methodName + ':' + pos.lineNumber + ':\n';
data += if(msg.toLowerCase().indexOf('error') != -1) RED else YELLOW;
data += msg.toString() + '\n' + DEFAULT;
Debug.instance.socket.send(data);
}
else
{
Debug.instance.Display('no connection to debug-server\n' + msg);
}
}
// properties
private var connected: Bool;
private var socket: XMLSocket;
private var tfDisplay: TextField;
private var ConnectHandler: Void->Void;
// constructor
public function new() { }
// methods
private function Display(msg:String):Void
{
if(this.tfDisplay == null)
{
this.tfDisplay = new TextField();
this.tfDisplay.defaultTextFormat = new TextFormat(null, 24, 0xff0000);
this.tfDisplay.autoSize = 'left';
this.tfDisplay.border = true;
this.tfDisplay.text = '';
flash.Lib.current.addChild(this.tfDisplay);
}
this.tfDisplay.text += msg + '\n';
}
private function Connect():Void
{
this.connected = false;
try
{
Security.loadPolicyFile('xmlsocket://localhost:4444');
this.socket = new XMLSocket('localhost', 4444);
this.socket.addEventListener(Event.CONNECT, OnConnect);
this.socket.addEventListener(Event.CLOSE, OnClose);
this.socket.addEventListener(IOErrorEvent.IO_ERROR, OnIOError);
this.socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, OnSecurityError);
}
catch(e:Dynamic) { this.Display(e.toString()); }
}
private function OnConnect(event:Event):Void
{
this.connected = true;
this.ConnectHandler();
}
private function OnClose(event:Event):Void
{
this.Display('lost connection to debug-server');
}
private function OnIOError(event:IOErrorEvent):Void
{
this.Display(event.toString());
}
private function OnSecurityError(event:SecurityErrorEvent):Void
{
this.Display(event.toString());
}
public function toString():String { return 'Debug'; }
}
Здесь все немного сложнее. Во-первых, что будет, если дебаг-сервер не доступен (не запущен)? Нужно как-то сообщить об этом и как-то показывать сообщения. Поэтому у нас будет резервная система. Эту роль выполняет метод
Display
private function Display(msg:String):Void
{
if(this.tfDisplay == null)
{
this.tfDisplay = new TextField();
this.tfDisplay.defaultTextFormat = new TextFormat(null, 24, 0xff0000);
this.tfDisplay.autoSize = 'left';
this.tfDisplay.border = true;
this.tfDisplay.text = '';
flash.Lib.current.addChild(this.tfDisplay);
}
this.tfDisplay.text += msg + '\n';
}
Он создает текстовое поле в корневом мувике (если оно не создано), и выводит сообщения туда.
Все начинается с метода Init
static public function Init(ConnectHandler:Void->Void):Void
{
Debug.instance = new Debug();
Debug.instance.ConnectHandler = ConnectHandler;
Debug.instance.Connect();
haxe.Log.trace = Debug.Trace;
}
Здесь создается экземпляр класса
Debug. Сохраняется ссылка на метод, который нужно будет вызвать, когда Debug будет готов к работе, устанавливается соединение с дебаг-сервером и переопределяется
haxe.Log.trace
Установка соединения уже рассматривалась в статье про XMLSocket. После того, как соединение установлено, вызывается ConnectHandler, которым, в данном случае, является метод TreeManApp.Start.
Наконец, метод Trace, который заменяет собой haxe.Log.trace.
static public function Trace(data:Dynamic, ?pos:haxe.PosInfos):Void
{
var msg = data.toString();
if(Debug.instance.connected)
{
var data = BLUE;
data += pos.className + '.' + pos.methodName + ':' + pos.lineNumber + ':\n';
data += if(msg.toLowerCase().indexOf('error') != -1) RED else YELLOW;
data += msg.toString() + '\n' + DEFAULT;
Debug.instance.socket.send(data);
}
else
{
Debug.instance.Display('no connection to debug-server\n' + msg);
}
}
Если нет соединения с дебаг-сервером, сообщение передается методу
Display. А в нормальном режиме формируется сообщение и отправляется дебаг-серверу. Сообщение раскрашивается цветами: PosInfo -- синим, msg -- желтым. Если сообщение содержит подскроку "error", то оно окрашивается красным. Для этого используются коды терминала. Это работает только в Linux, в Windows терминал, к сожалению, нельзя расскрасить разными цветами.
Вот и все. В одном окне открываем терминал и запускаем дебаг-сервер, в другом окне (в браузере или в stand-alone плеере) запускаем флэшку. Любуемся сообщениями в терминале. В аттаче пара скриншотов, как это выглядит у меня.