package org.mangui.hls
{
    import flash.external.ExternalInterface;
    import org.hola.ZExternalInterface;
    import org.hola.JSAPI;
    import org.hola.HSettings;
    import org.mangui.hls.event.HLSEvent;

    public class HLSJS
    {
        private static var _inited:Boolean = false;
        private static var _duration:Number;
        private static var _bandwidth:Number = -1;
        private static var _url:String;
        private static var _hls:HLS;

        public static function init():void{
            if (_inited || !ZExternalInterface.avail())
                return;
            _inited = true;
            JSAPI.init();
            ExternalInterface.addCallback("hola_version", hola_version);
            ExternalInterface.addCallback("hola_hls_get_video_url",
                hola_hls_get_video_url);
            ExternalInterface.addCallback("hola_hls_get_position",
                hola_hls_get_position);
            ExternalInterface.addCallback("hola_hls_get_duration",
                hola_hls_get_duration);
            ExternalInterface.addCallback("hola_hls_get_buffer_sec",
                hola_hls_get_buffer_sec);
            ExternalInterface.addCallback("hola_hls_get_state",
                hola_hls_get_state);
            ExternalInterface.addCallback("hola_hls_get_levels",
                hola_hls_get_levels);
            ExternalInterface.addCallback("hola_hls_get_level",
                hola_hls_get_level);
            ExternalInterface.addCallback("hola_setBandwidth",
                hola_setBandwidth);
        }

        public static function HLSnew(hls:HLS):void{
            _hls = hls;
            // notify js events
            hls.addEventListener(HLSEvent.MANIFEST_LOADING, on_event);
            hls.addEventListener(HLSEvent.MANIFEST_PARSED, on_event);
            hls.addEventListener(HLSEvent.MANIFEST_LOADED, on_event);
            hls.addEventListener(HLSEvent.LEVEL_LOADING, on_event);
            hls.addEventListener(HLSEvent.LEVEL_LOADING_ABORTED, on_event);
            hls.addEventListener(HLSEvent.LEVEL_LOADED, on_event);
            hls.addEventListener(HLSEvent.LEVEL_SWITCH, on_event);
            hls.addEventListener(HLSEvent.LEVEL_ENDLIST, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_LOADING, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_LOADED, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED,
                on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_PLAYING, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_SKIPPED, on_event);
            hls.addEventListener(HLSEvent.AUDIO_TRACKS_LIST_CHANGE, on_event);
            hls.addEventListener(HLSEvent.AUDIO_TRACK_SWITCH, on_event);
            hls.addEventListener(HLSEvent.AUDIO_LEVEL_LOADING, on_event);
            hls.addEventListener(HLSEvent.AUDIO_LEVEL_LOADED, on_event);
            hls.addEventListener(HLSEvent.TAGS_LOADED, on_event);
            hls.addEventListener(HLSEvent.LAST_VOD_FRAGMENT_LOADED, on_event);
            hls.addEventListener(HLSEvent.WARNING, on_event);
            hls.addEventListener(HLSEvent.ERROR, on_event);
            hls.addEventListener(HLSEvent.MEDIA_TIME, on_event);
            hls.addEventListener(HLSEvent.PLAYBACK_STATE, on_event);
            hls.addEventListener(HLSEvent.SEEK_STATE, on_event);
            hls.addEventListener(HLSEvent.STREAM_TYPE_DID_CHANGE, on_event);
            hls.addEventListener(HLSEvent.PLAYBACK_COMPLETE, on_event);
            hls.addEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED,
                on_event);
            hls.addEventListener(HLSEvent.ID3_UPDATED, on_event);
            hls.addEventListener(HLSEvent.FPS_DROP, on_event);
            hls.addEventListener(HLSEvent.FPS_DROP_LEVEL_CAPPING, on_event);
            hls.addEventListener(HLSEvent.FPS_DROP_SMOOTH_LEVEL_SWITCH,
                on_event);
            hls.addEventListener(HLSEvent.LIVE_LOADING_STALLED, on_event);
            // track duration events
            hls.addEventListener(HLSEvent.MANIFEST_LOADED, on_manifest_loaded);
            hls.addEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED,
                on_playlist_duration_updated);
            // track playlist url
            hls.addEventListener(HLSEvent.MANIFEST_LOADING, on_manifest_loading);
            JSAPI.postMessage2({id: 'flashls.hlsNew', hls_id: hls.id});
        }

        public static function HLSdispose(hls:HLS):void{
            JSAPI.postMessage2({id: 'flashls.hlsDispose', hls_id: hls.id});
            _duration = 0;
            _bandwidth = -1;
            _url = null;
            _hls = null;
        }

        public static function get bandwidth():Number{
            return HSettings.hls_mode ? _bandwidth : -1;
        }

        private static function on_manifest_loaded(e:HLSEvent):void{
            _duration = e.levels[_hls.startLevel].duration;
        }

        private static function on_playlist_duration_updated(e:HLSEvent):void{
            _duration = e.duration;
        }

        private static function on_manifest_loading(e:HLSEvent):void{
            _url = e.url;
        }

        private static function on_event(e:HLSEvent):void{
            JSAPI.postMessage2({id: 'flashls.'+e.type, hls_id: _hls.id,
                url: e.url, level: e.level, duration: e.duration, levels: e.levels,
                error: e.error, loadMetrics: e.loadMetrics,
                playMetrics: e.playMetrics, mediatime: e.mediatime, state: e.state,
                audioTrack: e.audioTrack, streamType: e.streamType});
        }

        private static function hola_version():Object{
            return {
                flashls_version: '0.4.2.1',
                patch_version: '2.0.2'
            };
        }

        private static function hola_hls_get_video_url():String{
            return _url;
        }

        private static function hola_hls_get_position():Number{
            return _hls.position;
        }

        private static function hola_hls_get_duration():Number{
            return _duration;
        }

        private static function hola_hls_get_buffer_sec():Number{
            return _hls.stream.bufferLength;
        }

        private static function hola_hls_get_state():String{
            return _hls.playbackState;
        }

        private static function hola_hls_get_levels():Object{
            return _hls.levels;
        }

        private static function hola_hls_get_level():Number{
            return _hls.loadLevel;
        }

        private static function hola_setBandwidth(bandwidth:Number):void{
            _bandwidth = bandwidth;
        }
    }
}

package org.mangui.hls
{
    import flash.external.ExternalInterface;
    import org.hola.ZExternalInterface;
    import org.hola.JSAPI;
    import org.hola.HSettings;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.constant.HLSPlayStates;

    public class HLSJS
    {
        private static var _inited:Boolean = false;
        private static var _duration:Number;
        private static var _bandwidth:Number = -1;
        private static var _url:String;
        private static var _state:String;
        private static var _hls:HLS;

        public static function init():void{
            if (_inited || !ZExternalInterface.avail())
                return;
            _inited = true;
            JSAPI.init();
            ExternalInterface.addCallback("hola_version", hola_version);
            ExternalInterface.addCallback("hola_hls_get_video_url",
                hola_hls_get_video_url);
            ExternalInterface.addCallback("hola_hls_get_position",
                hola_hls_get_position);
            ExternalInterface.addCallback("hola_hls_get_duration",
                hola_hls_get_duration);
            ExternalInterface.addCallback("hola_hls_get_buffer_sec",
                hola_hls_get_buffer_sec);
            ExternalInterface.addCallback("hola_hls_get_state",
                hola_hls_get_state);
            ExternalInterface.addCallback("hola_hls_get_levels",
                hola_hls_get_levels);
            ExternalInterface.addCallback("hola_hls_get_level",
                hola_hls_get_level);
            ExternalInterface.addCallback("hola_setBandwidth",
                hola_setBandwidth);
        }

        public static function HLSnew(hls:HLS):void{
            _hls = hls;
            _state = HLSPlayStates.IDLE;
            // track duration events
            hls.addEventListener(HLSEvent.MANIFEST_LOADED, on_manifest_loaded);
            hls.addEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED,
                on_playlist_duration_updated);
            // track playlist-url/state
            hls.addEventListener(HLSEvent.MANIFEST_LOADING, on_manifest_loading);
            hls.addEventListener(HLSEvent.PLAYBACK_STATE, on_playback_state);
            // notify js events
            hls.addEventListener(HLSEvent.MANIFEST_LOADING, on_event);
            hls.addEventListener(HLSEvent.MANIFEST_PARSED, on_event);
            hls.addEventListener(HLSEvent.MANIFEST_LOADED, on_event);
            hls.addEventListener(HLSEvent.LEVEL_LOADING, on_event);
            hls.addEventListener(HLSEvent.LEVEL_LOADING_ABORTED, on_event);
            hls.addEventListener(HLSEvent.LEVEL_LOADED, on_event);
            hls.addEventListener(HLSEvent.LEVEL_SWITCH, on_event);
            hls.addEventListener(HLSEvent.LEVEL_ENDLIST, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_LOADING, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_LOADED, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED,
                on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_PLAYING, on_event);
            hls.addEventListener(HLSEvent.FRAGMENT_SKIPPED, on_event);
            hls.addEventListener(HLSEvent.AUDIO_TRACKS_LIST_CHANGE, on_event);
            hls.addEventListener(HLSEvent.AUDIO_TRACK_SWITCH, on_event);
            hls.addEventListener(HLSEvent.AUDIO_LEVEL_LOADING, on_event);
            hls.addEventListener(HLSEvent.AUDIO_LEVEL_LOADED, on_event);
            hls.addEventListener(HLSEvent.TAGS_LOADED, on_event);
            hls.addEventListener(HLSEvent.LAST_VOD_FRAGMENT_LOADED, on_event);
            hls.addEventListener(HLSEvent.WARNING, on_event);
            hls.addEventListener(HLSEvent.ERROR, on_event);
            hls.addEventListener(HLSEvent.MEDIA_TIME, on_event);
            hls.addEventListener(HLSEvent.PLAYBACK_STATE, on_event);
            hls.addEventListener(HLSEvent.SEEK_STATE, on_event);
            hls.addEventListener(HLSEvent.STREAM_TYPE_DID_CHANGE, on_event);
            hls.addEventListener(HLSEvent.PLAYBACK_COMPLETE, on_event);
            hls.addEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED,
                on_event);
            hls.addEventListener(HLSEvent.ID3_UPDATED, on_event);
            hls.addEventListener(HLSEvent.FPS_DROP, on_event);
            hls.addEventListener(HLSEvent.FPS_DROP_LEVEL_CAPPING, on_event);
            hls.addEventListener(HLSEvent.FPS_DROP_SMOOTH_LEVEL_SWITCH,
                on_event);
            hls.addEventListener(HLSEvent.LIVE_LOADING_STALLED, on_event);
            JSAPI.postMessage2({id: 'flashls.hlsNew', hls_id: hls.id});
        }

        public static function HLSdispose(hls:HLS):void{
            JSAPI.postMessage2({id: 'flashls.hlsDispose', hls_id: hls.id});
            _duration = 0;
            _bandwidth = -1;
            _url = null;
            _state = HLSPlayStates.IDLE;
            _hls = null;
        }

        public static function get bandwidth():Number{
            return HSettings.hls_mode ? _bandwidth : -1;
        }

        private static function on_manifest_loaded(e:HLSEvent):void{
            _duration = e.levels[_hls.startLevel].duration;
        }

        private static function on_playlist_duration_updated(e:HLSEvent):void{
            _duration = e.duration;
        }

        private static function on_manifest_loading(e:HLSEvent):void{
            _url = e.url;
            _state = "LOADING";
            on_event(new HLSEvent(HLSEvent.PLAYBACK_STATE, _state));
        }

        private static function on_playback_state(e:HLSEvent):void{
            _state = e.state;
        }

        private static function on_event(e:HLSEvent):void{
            JSAPI.postMessage2({id: 'flashls.'+e.type, hls_id: _hls.id,
                url: e.url, level: e.level, duration: e.duration, levels: e.levels,
                error: e.error, loadMetrics: e.loadMetrics,
                playMetrics: e.playMetrics, mediatime: e.mediatime, state: e.state,
                audioTrack: e.audioTrack, streamType: e.streamType});
        }

        private static function hola_version():Object{
            return {
                flashls_version: '0.4.2.1',
                patch_version: '2.0.2'
            };
        }

        private static function hola_hls_get_video_url():String{
            return _url;
        }

        private static function hola_hls_get_position():Number{
            return _hls.position;
        }

        private static function hola_hls_get_duration():Number{
            return _duration;
        }

        private static function hola_hls_get_buffer_sec():Number{
            return _hls.stream.bufferLength;
        }

        private static function hola_hls_get_state():String{
            return _state;
        }

        private static function hola_hls_get_levels():Object{
            return _hls.levels;
        }

        private static function hola_hls_get_level():Number{
            return _hls.loadLevel;
        }

        private static function hola_setBandwidth(bandwidth:Number):void{
            _bandwidth = bandwidth;
        }
    }
}

add LOADING state

add LOADING state


package aerys.minko.render.geometry.stream
{
	import aerys.minko.ns.minko_stream;
	import aerys.minko.render.resource.IndexBuffer3DResource;
	import aerys.minko.type.Signal;

	import flash.utils.ByteArray;
	import flash.utils.Endian;

	public final class IndexStream
	{
		use namespace minko_stream;

		minko_stream var _data			: ByteArray;
		minko_stream var _localDispose	: Boolean;

		private var _usage		: uint;
		private var _resource	: IndexBuffer3DResource;
		private var _length		: uint;

		private var _locked		: Boolean;

		private var _changed	: Signal;

		public function get usage() : uint
		{
			return _usage;
		}

		public function get resource() : IndexBuffer3DResource
		{
			return _resource;
		}

		public function get length() : uint
		{
			return _length;
		}

		public function set length(value : uint) : void
		{
			_data.length = value << 1;
			invalidate();
		}

		public function get changed() : Signal
		{
			return _changed;
		}

		public function IndexStream(usage	: uint,
									data 	: ByteArray	= null,
									length	: uint		= 0)
		{
			super();

			initialize(data, length, usage);
		}

		minko_stream function invalidate() : void
		{
			_data.position = 0;
			_length = _data.length >>> 1;

            if (_length % 3 != 0)
                throw new Error('Invalid size');

			if (!_locked)
				_changed.execute(this);
		}

		private function initialize(data	: ByteArray,
									length 	: uint,
									usage	: uint) : void
		{
			_changed = new Signal('IndexStream.changed');

			_usage = usage;
			_resource = new IndexBuffer3DResource(this);

			_data = new ByteArray();
			_data.endian = Endian.LITTLE_ENDIAN;
			if (data)
			{
				if (data.endian != Endian.LITTLE_ENDIAN)
					throw new Error('Endianness must be Endian.LITTLE_ENDIAN.');
				if (length == 0)
					length = data.bytesAvailable;
				if (length % 6 != 0)
					throw new Error('Invalid size');

				data.readBytes(_data, 0, length);
			}
			else
			{
				_data = dummyData(length);
			}

			_data.position = 0;

			invalidate();
		}

		public function get(index : uint) : uint
		{
			var value : uint = 0;

			checkReadUsage(this);

			_data.position = index << 1;
			value = _data.readUnsignedShort();
			_data.position = 0;

			return value;
		}

		public function set(index : uint, value : uint) : void
		{
			checkWriteUsage(this);

			_data.position = index << 1;
			_data.writeShort(value);
			_data.position = 0;

			invalidate();
		}

		public function deleteTriangle(triangleIndex : uint) : void
		{
			checkWriteUsage(this);

			_data.position = 0;
			_data.writeBytes(_data, triangleIndex * 12, 12);
			_data.length -= 12;
			_data.position = 0;

			invalidate();
		}

		public function clone(usage : uint = 0) : IndexStream
		{
			return new IndexStream(usage || _usage, _data);
		}

		public function toString() : String
		{
			return _data.toString();
		}

		public function concat(indexStream : IndexStream,
							   firstIndex	: uint	= 0,
							   count		: uint	= 0,
							   indexOffset	: uint 	= 0) : IndexStream
		{
			checkReadUsage(indexStream);
			checkWriteUsage(this);

			pushBytes(indexStream._data, firstIndex, count, indexOffset);
			indexStream._data.position = 0;

			return this;
		}

		public function pushBytes(bytes			: ByteArray,
								  firstIndex	: uint	= 0,
								  count			: uint	= 0,
								  indexOffset	: uint	= 0) : IndexStream
		{
			count ||= bytes.length >>> 1;

			_data.position = _data.length;

			if (indexOffset == 0)
				_data.writeBytes(bytes, firstIndex << 1, count << 1);
			else
			{
				bytes.position = firstIndex << 1;
				for (var i : uint = 0; i < count; ++i)
					_data.writeShort(bytes.readUnsignedShort() + indexOffset);
			}

			_data.position = 0;
			bytes.position = (firstIndex + count) << 1;

			invalidate();

			return this;
		}

		public function pushVector(indices 		: Vector.<uint>,
								   firstIndex	: uint	= 0,
								   count		: uint	= 0,
								   offset		: uint 	= 0) : IndexStream
		{
			checkWriteUsage(this);

			var numIndices : int = _data.length;

			count ||= indices.length;

			_data.position = _data.length;
			for (var i : int = 0; i < count; ++i)
				_data.writeShort(indices[int(firstIndex + i)] + offset);
			_data.position = 0;

			invalidate();

			return this;
		}

		public function pushTriangle(index1 : uint, index2 : uint, index3 : uint) : IndexStream
		{
			return setTriangle(length * 3, index1, index2, index3);
		}

		public function setTriangle(triangleIndex	: uint,
									index1 			: uint,
									index2 			: uint,
									index3 			: uint) : IndexStream
		{
			_data.position = triangleIndex << 1;
			_data.writeShort(index1);
			_data.writeShort(index2);
			_data.writeShort(index3);
			_data.position = 0;

			invalidate();

			return this;
		}

		public function disposeLocalData(waitForUpload : Boolean = true) : void
		{
			if (waitForUpload && _length != resource.numIndices)
				_localDispose = true;
			else
			{
				_data = null;
				_usage = StreamUsage.STATIC;
			}
		}

		public function dispose() : void
		{
			_resource.dispose();
		}

		public function lock() : ByteArray
		{
			checkReadUsage(this);

			_data.position = 0;
			_locked = true;

			return _data;
		}

		public function unlock(hasChanged : Boolean = true) : void
		{
			_data.position = 0;

			_locked = false;

			if (hasChanged)
				invalidate();
		}

		/**
		 * Clones the IndexStream by creating a new underlying ByteArray and applying an offset on the indices.
		 *
		 * @param indexStream The IndexStream to clone.
		 * @param offset The offset to apply on the indices.
		 *
		 * @return The offseted new IndexStream.
		 *
		 */
		public static function cloneWithOffset(indexStream	: IndexStream,
											   offset 		: uint 			= 0) : IndexStream
		{
			if (!(indexStream.usage & StreamUsage.READ))
			{
				throw new Error('Unable to read from index stream: stream usage is not set to StreamUsage.READ.');
			}

			var newData		: ByteArray	= new ByteArray();
			newData.endian 				= Endian.LITTLE_ENDIAN;

			var oldData		: ByteArray	= indexStream._data;
			oldData.position = 0;
			while (oldData.bytesAvailable)
			{
				var index	: uint		= oldData.readUnsignedShort() + offset;
				newData.writeShort(index);
			}
			newData.position = 0;

			return new IndexStream(indexStream.usage, newData);
		}

		private static function checkReadUsage(stream : IndexStream) : void
		{
			if (!(stream._usage & StreamUsage.READ))
				throw new Error(
					'Unable to read from vertex stream: stream usage is not set to StreamUsage.READ.'
				);
		}

		private static function checkWriteUsage(stream : IndexStream) : void
		{
			if (!(stream._usage & StreamUsage.WRITE))
				throw new Error(
					'Unable to write in vertex stream: stream usage is not set to StreamUsage.WRITE.'
				);
		}

		public static function dummyData(size 	: uint,
										 offset : uint = 0) : ByteArray
		{
			var indices : ByteArray = new ByteArray();

			indices.endian = Endian.LITTLE_ENDIAN;
			for (var i : int = 0; i < size; ++i)
				indices.writeShort(i + offset);
			indices.position = 0;

			return indices;
		}

		public static function fromVector(usage : uint, data : Vector.<uint>) : IndexStream
		{
			var stream 		: IndexStream 	= new IndexStream(usage);
			var numIndices 	: uint 			= data.length;

			for (var i : uint = 0; i < numIndices; ++i)
				stream._data.writeShort(data[i]);
			stream._data.position = 0;
			stream._length = numIndices;

			return stream;
		}
	}
}

package aerys.minko.render.geometry.stream
{
	import aerys.minko.ns.minko_stream;
	import aerys.minko.render.resource.IndexBuffer3DResource;
	import aerys.minko.type.Signal;

	import flash.utils.ByteArray;
	import flash.utils.Endian;

	public final class IndexStream
	{
		use namespace minko_stream;

		minko_stream var _data			: ByteArray;
		minko_stream var _localDispose	: Boolean;

		private var _usage		: uint;
		private var _resource	: IndexBuffer3DResource;
		private var _length		: uint;

		private var _locked		: Boolean;

		private var _changed	: Signal;

		public function get usage() : uint
		{
			return _usage;
		}

		public function get resource() : IndexBuffer3DResource
		{
			return _resource;
		}

		public function get length() : uint
		{
			return _length;
		}

		public function set length(value : uint) : void
		{
			_data.length = value << 1;
			invalidate();
		}

		public function get changed() : Signal
		{
			return _changed;
		}

		public function IndexStream(usage	: uint,
									data 	: ByteArray	= null,
									length	: uint		= 0)
		{
			super();

			initialize(data, length, usage);
		}

		minko_stream function invalidate() : void
		{
			_data.position = 0;
			_length = _data.length >>> 1;

            if (_length % 3 != 0)
                throw new Error('Invalid size');

			if (!_locked)
				_changed.execute(this);
		}

		private function initialize(data	: ByteArray,
									length 	: uint,
									usage	: uint) : void
		{
			_changed = new Signal('IndexStream.changed');

			_usage = usage;
			_resource = new IndexBuffer3DResource(this);

			_data = new ByteArray();
			_data.endian = Endian.LITTLE_ENDIAN;
			if (data)
			{
				if (data.endian != Endian.LITTLE_ENDIAN)
					throw new Error('Endianness must be Endian.LITTLE_ENDIAN.');
				if (length == 0)
					length = data.bytesAvailable;
				if (length % 6 != 0)
					throw new Error('Invalid size');

				data.readBytes(_data, 0, length);
			}
			else
			{
				_data = dummyData(length);
			}

			_data.position = 0;

			invalidate();
		}

		public function get(index : uint) : uint
		{
			var value : uint = 0;

			checkReadUsage(this);

			_data.position = index << 1;
			value = _data.readUnsignedShort();
			_data.position = 0;

			return value;
		}

		public function set(index : uint, value : uint) : void
		{
			checkWriteUsage(this);

			_data.position = index << 1;
			_data.writeShort(value);
			_data.position = 0;

			invalidate();
		}

		public function deleteTriangle(triangleIndex : uint) : void
		{
			checkWriteUsage(this);

			_data.position = _data.length - 6;
			var v0 : uint = _data.readShort();
			var v1 : uint = _data.readShort();
			var v2 : uint = _data.readShort();

			_data.position = triangleIndex * 12;
			_data.writeShort(v0);
			_data.writeShort(v1);
			_data.writeShort(v2);

//			_data.writeBytes(_data, triangleIndex * 12, 12);
			_data.length -= 6;
			_data.position = 0;

			invalidate();
		}

		public function clone(usage : uint = 0) : IndexStream
		{
			return new IndexStream(usage || _usage, _data);
		}

		public function toString() : String
		{
			return _data.toString();
		}

		public function concat(indexStream : IndexStream,
							   firstIndex	: uint	= 0,
							   count		: uint	= 0,
							   indexOffset	: uint 	= 0) : IndexStream
		{
			checkReadUsage(indexStream);
			checkWriteUsage(this);

			pushBytes(indexStream._data, firstIndex, count, indexOffset);
			indexStream._data.position = 0;

			return this;
		}

		public function pushBytes(bytes			: ByteArray,
								  firstIndex	: uint	= 0,
								  count			: uint	= 0,
								  indexOffset	: uint	= 0) : IndexStream
		{
			count ||= bytes.length >>> 1;

			_data.position = _data.length;

			if (indexOffset == 0)
				_data.writeBytes(bytes, firstIndex << 1, count << 1);
			else
			{
				bytes.position = firstIndex << 1;
				for (var i : uint = 0; i < count; ++i)
					_data.writeShort(bytes.readUnsignedShort() + indexOffset);
			}

			_data.position = 0;
			bytes.position = (firstIndex + count) << 1;

			invalidate();

			return this;
		}

		public function pushVector(indices 		: Vector.<uint>,
								   firstIndex	: uint	= 0,
								   count		: uint	= 0,
								   offset		: uint 	= 0) : IndexStream
		{
			checkWriteUsage(this);

			var numIndices : int = _data.length;

			count ||= indices.length;

			_data.position = _data.length;
			for (var i : int = 0; i < count; ++i)
				_data.writeShort(indices[int(firstIndex + i)] + offset);
			_data.position = 0;

			invalidate();

			return this;
		}

		public function pushTriangle(index1 : uint, index2 : uint, index3 : uint) : IndexStream
		{
			return setTriangle(length * 3, index1, index2, index3);
		}

		public function setTriangle(triangleIndex	: uint,
									index1 			: uint,
									index2 			: uint,
									index3 			: uint) : IndexStream
		{
			_data.position = triangleIndex << 1;
			_data.writeShort(index1);
			_data.writeShort(index2);
			_data.writeShort(index3);
			_data.position = 0;

			invalidate();

			return this;
		}

		public function disposeLocalData(waitForUpload : Boolean = true) : void
		{
			if (waitForUpload && _length != resource.numIndices)
				_localDispose = true;
			else
			{
				_data = null;
				_usage = StreamUsage.STATIC;
			}
		}

		public function dispose() : void
		{
			_resource.dispose();
		}

		public function lock() : ByteArray
		{
			checkReadUsage(this);

			_data.position = 0;
			_locked = true;

			return _data;
		}

		public function unlock(hasChanged : Boolean = true) : void
		{
			_data.position = 0;

			_locked = false;

			if (hasChanged)
				invalidate();
		}

		/**
		 * Clones the IndexStream by creating a new underlying ByteArray and applying an offset on the indices.
		 *
		 * @param indexStream The IndexStream to clone.
		 * @param offset The offset to apply on the indices.
		 *
		 * @return The offseted new IndexStream.
		 *
		 */
		public static function cloneWithOffset(indexStream	: IndexStream,
											   offset 		: uint 			= 0) : IndexStream
		{
			if (!(indexStream.usage & StreamUsage.READ))
			{
				throw new Error('Unable to read from index stream: stream usage is not set to StreamUsage.READ.');
			}

			var newData		: ByteArray	= new ByteArray();
			newData.endian 				= Endian.LITTLE_ENDIAN;

			var oldData		: ByteArray	= indexStream._data;
			oldData.position = 0;
			while (oldData.bytesAvailable)
			{
				var index	: uint		= oldData.readUnsignedShort() + offset;
				newData.writeShort(index);
			}
			newData.position = 0;

			return new IndexStream(indexStream.usage, newData);
		}

		private static function checkReadUsage(stream : IndexStream) : void
		{
			if (!(stream._usage & StreamUsage.READ))
				throw new Error(
					'Unable to read from vertex stream: stream usage is not set to StreamUsage.READ.'
				);
		}

		private static function checkWriteUsage(stream : IndexStream) : void
		{
			if (!(stream._usage & StreamUsage.WRITE))
				throw new Error(
					'Unable to write in vertex stream: stream usage is not set to StreamUsage.WRITE.'
				);
		}

		public static function dummyData(size 	: uint,
										 offset : uint = 0) : ByteArray
		{
			var indices : ByteArray = new ByteArray();

			indices.endian = Endian.LITTLE_ENDIAN;
			for (var i : int = 0; i < size; ++i)
				indices.writeShort(i + offset);
			indices.position = 0;

			return indices;
		}

		public static function fromVector(usage : uint, data : Vector.<uint>) : IndexStream
		{
			var stream 		: IndexStream 	= new IndexStream(usage);
			var numIndices 	: uint 			= data.length;

			for (var i : uint = 0; i < numIndices; ++i)
				stream._data.writeShort(data[i]);
			stream._data.position = 0;
			stream._length = numIndices;

			return stream;
		}
	}
}

Fix delete triangle function

Fix delete triangle function


/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.data.source
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableHashMap;
	import weavejs.api.core.ILinkableVariable;
	import weavejs.api.data.IKeySet;
	import weavejs.api.data.Aggregation;
	import weavejs.api.data.ColumnMetadata;
	import weavejs.api.data.DataType;
	import weavejs.api.data.IAttributeColumn;
	import weavejs.api.data.IDataSource;
	import weavejs.api.data.IQualifiedKey;
	import weavejs.api.data.ISelectableAttributes;
	import weavejs.api.data.IWeaveTreeNode;
	import weavejs.core.LinkableHashMap;
	import weavejs.core.LinkableString;
	import weavejs.core.LinkableVariable;
	import weavejs.core.LinkableBoolean;
	import weavejs.core.LinkableNumber;
	import weavejs.data.column.DynamicColumn;
	import weavejs.data.column.ProxyColumn;
	import weavejs.data.column.StringColumn;
	import weavejs.data.hierarchy.ColumnTreeNode;
	import weavejs.data.ColumnUtils;
	import weavejs.data.DataSourceUtils;
	import weavejs.data.key.DynamicKeyFilter;
	import weavejs.util.JS;
	import weavejs.util.StandardLib;

	public class WebSocketDataSource extends AbstractDataSource
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, WebSocketDataSource, "WebSocket Data Source");

		public static const DATA_COLUMNNAME_META:String = "__WebSocketJsonPropertyName__";

		public function WebSocketDataSource()
		{
			this.selectionFilter.targetPath = ["defaultSelectionKeySet"];
		}

		public const keyType:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keyProperty:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keepLast:LinkableNumber = Weave.linkableChild(this, LinkableNumber, onKeepLastChange);
		public const selectionFilter:DynamicKeyFilter = Weave.linkableChild(this, DynamicKeyFilter, onSelectionFilterChange);
		//public const selectionFeedback:LinkableBoolean = Weave.linkableChild(this, LinkableBoolean);
		public const url:LinkableString = Weave.linkableChild(this, LinkableString, onUrlChange);

		override protected function initialize(forceRefresh:Boolean = false):void
		{
			// recalculate all columns previously requested because CSV data may have changed.
			super.initialize(true);
		}

		private function onSelectionFilterChange():void
		{
			if (_socket && _socket.readyState == 1)
			{
				var ks:IKeySet = selectionFilter.getInternalKeyFilter() as IKeySet;
				_socket.send(JSON.stringify(ks.keys.map(function (key:IQualifiedKey, idx:int, a:Array):String {
					return key.localName;
				})));
			}
		}

		private var _socket:Object = null;
		private function onUrlChange():void
		{
			reconnect();

			records = [];
			propertyNames.clear();
		}

		public function reconnect():void /* To be called from UI reconnect button. */
		{
			JS.global.clearTimeout(timeoutId);
			if (_socket && _socket.readyState < 2)
			{
				_socket.close(1000, "No longer needed.");
				_socket = null;
			}

			_socket = new JS.global.WebSocket(url.value);

			_socket.onmessage = onMessage;
			_socket.onclose = onClose;
			_socket.onerror = onError;
		}

		private var timeoutId:int = 0;
		private function onClose(event:Object):void
		{
			_socket = null;
			if (url.value && event.code == 1006 /* CLOSE_ABNORMAL */)
			{
				timeoutId = JS.global.setTimeout(reconnect, 1500)
			}
		}

		private function onError(event:Object):void
		{
			JS.error(event);
			/* Should we automatically retry here? */
			_socket = null;
		}

		private function addRecord(record:Object):void
		{
			if ((records.length == keepLast.value) && (keepLast.value > 0))
			{
				records.shift();
			}

			records.push(record);

			/* Update set of property names */
			for each (var key:String in JS.objectKeys(record))
			{
				propertyNames.add(key);
			}
		}

		private function onMessage(event:/*/MessageEvent/*/Object):void
		{
			var str:String = event.data;
			var message:Object = JSON.parse(str);

			if (message is Array)
			{
				message.forEach(addRecord);
			}
			else
			{
				addRecord(message);
			}

			Weave.getCallbacks(this).triggerCallbacks();
		}

		public function getPropertyNames():Array/*/<string>/*/
		{
			return JS.mapKeys(propertyNames)
		}

		private function onKeepLastChange():void
		{
			var diff:int = records.length - keepLast.value;
			if (diff > 0)
			{
				records = records.slice(diff);
			}
		}

		/**
		 * The session state maps a column name in dataColumns hash map to a value for its "aggregation" metadata.
		 */
		public const aggregationModes:ILinkableVariable = Weave.linkableChild(this, new LinkableVariable(null, typeofIsObject));
		private function typeofIsObject(value:Object):Boolean
		{
			return typeof value == 'object';
		}

		private var records:Array = [];
		private var propertyNames:Object = new JS.global.Set();

		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!_rootNode)
				_rootNode = new ColumnTreeNode({
					cacheSettings: {"label": false},
					dataSource: this,
					dependency: this,
					data: this,
					"label": getLabel,
					hasChildBranches: false,
					children: function():Array {
						return JS.mapValues(propertyNames).map(
							function (columnName:String, ..._):* {
								var meta:Object = {};
								meta[DATA_COLUMNNAME_META] = columnName;
								return generateHierarchyNode(meta);
							}
						);
					}
				});
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (!metadata)
				return null;

			metadata = getColumnMetadata(metadata[DATA_COLUMNNAME_META]);

			if (!metadata)
				return null;

			return new ColumnTreeNode({
				dataSource: this,
				idFields: [DATA_COLUMNNAME_META],
				data: metadata
			});
		}

		private function getColumnMetadata(dataColumnName:String):Object
		{
			var metadata:Object = {};
			metadata[ColumnMetadata.KEY_TYPE] = keyType.value || DataType.STRING;
			metadata[DATA_COLUMNNAME_META] = dataColumnName;
			metadata[ColumnMetadata.TITLE] = dataColumnName;

			var aggState:Object = aggregationModes.getSessionState();
			var aggregation:String = aggState ? aggState[dataColumnName] : null;
			aggregation = aggregation || Aggregation.LAST; /* Keep the last value by default, this makes more sense in streaming applications. */
			metadata[ColumnMetadata.AGGREGATION] = aggregation;

			if (aggregation != Aggregation.SAME)
				metadata[ColumnMetadata.TITLE] = Weave.lang("{0} ({1})", metadata[ColumnMetadata.TITLE], aggregation);

			return metadata;
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var columnName:String = proxyColumn.getMetadata(DATA_COLUMNNAME_META);

			var keys:Array;
			var data:Array = StandardLib.lodash.map(records, columnName);/*.map(
				function (d:*,i:int,a:Array):*
				{
					return d !== undefined ? d : null;
				}
			);*/

			if (keyProperty.value)
			{
				keys = StandardLib.lodash.map(records, keyProperty.value);
			}
			else
			{
				keys = StandardLib.lodash.range(data.length);
			}

			var metadata:Object = getColumnMetadata(columnName);

			var dataType:String = DataSourceUtils.guessDataType(data);
			metadata[ColumnMetadata.DATA_TYPE] = dataType;
			proxyColumn.setMetadata(metadata);

			DataSourceUtils.initColumn(proxyColumn, keys, data);
		}
	}
}

/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.data.source
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableHashMap;
	import weavejs.api.core.ILinkableVariable;
	import weavejs.api.data.IKeySet;
	import weavejs.api.data.Aggregation;
	import weavejs.api.data.ColumnMetadata;
	import weavejs.api.data.DataType;
	import weavejs.api.data.IAttributeColumn;
	import weavejs.api.data.IDataSource;
	import weavejs.api.data.IQualifiedKey;
	import weavejs.api.data.ISelectableAttributes;
	import weavejs.api.data.IWeaveTreeNode;
	import weavejs.core.LinkableHashMap;
	import weavejs.core.LinkableString;
	import weavejs.core.LinkableVariable;
	import weavejs.core.LinkableBoolean;
	import weavejs.core.LinkableNumber;
	import weavejs.data.column.DynamicColumn;
	import weavejs.data.column.ProxyColumn;
	import weavejs.data.column.StringColumn;
	import weavejs.data.hierarchy.ColumnTreeNode;
	import weavejs.data.ColumnUtils;
	import weavejs.data.DataSourceUtils;
	import weavejs.data.key.DynamicKeyFilter;
	import weavejs.util.JS;
	import weavejs.util.StandardLib;

	public class WebSocketDataSource extends AbstractDataSource
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, WebSocketDataSource, "WebSocket Data Source");

		public static const DATA_COLUMNNAME_META:String = "__WebSocketJsonPropertyName__";

		public function WebSocketDataSource()
		{
			this.selectionFilter.targetPath = ["defaultSelectionKeySet"];
		}

		public const keyType:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keyProperty:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keepLast:LinkableNumber = Weave.linkableChild(this, LinkableNumber, onKeepLastChange);
		public const selectionFilter:DynamicKeyFilter = Weave.linkableChild(this, DynamicKeyFilter, onSelectionFilterChange);
		//public const selectionFeedback:LinkableBoolean = Weave.linkableChild(this, LinkableBoolean);
		public const url:LinkableString = Weave.linkableChild(this, LinkableString, onUrlChange);

		override protected function initialize(forceRefresh:Boolean = false):void
		{
			// recalculate all columns previously requested because CSV data may have changed.
			super.initialize(true);
		}

		private function onSelectionFilterChange():void
		{
			if (_socket && _socket.readyState == 1)
			{
				var ks:IKeySet = selectionFilter.getInternalKeyFilter() as IKeySet;
				if (!ks || !ks.keys) return;
				_socket.send(JSON.stringify(ks.keys.map(function (key:IQualifiedKey, idx:int, a:Array):String {
					return key.localName;
				})));
			}
		}

		private var _socket:Object = null;
		private function onUrlChange():void
		{
			reconnect();

			records = [];
			propertyNames.clear();
		}

		public function reconnect():void /* To be called from UI reconnect button. */
		{
			JS.global.clearTimeout(timeoutId);
			if (_socket && _socket.readyState < 2)
			{
				_socket.close(1000, "No longer needed.");
				_socket = null;
			}

			_socket = new JS.global.WebSocket(url.value);

			_socket.onmessage = onMessage;
			_socket.onclose = onClose;
			_socket.onerror = onError;
		}

		private var timeoutId:int = 0;
		private function onClose(event:Object):void
		{
			_socket = null;
			if (url.value && event.code == 1006 /* CLOSE_ABNORMAL */)
			{
				timeoutId = JS.global.setTimeout(reconnect, 1500)
			}
		}

		private function onError(event:Object):void
		{
			JS.error(event);
			/* Should we automatically retry here? */
			_socket = null;
		}

		private function addRecord(record:Object):void
		{
			if ((records.length == keepLast.value) && (keepLast.value > 0))
			{
				records.shift();
			}

			records.push(record);

			/* Update set of property names */
			for each (var key:String in JS.objectKeys(record))
			{
				propertyNames.add(key);
			}
		}

		private function onMessage(event:/*/MessageEvent/*/Object):void
		{
			var str:String = event.data;
			var message:Object = JSON.parse(str);

			if (message is Array)
			{
				message.forEach(addRecord);
			}
			else
			{
				addRecord(message);
			}

			Weave.getCallbacks(this).triggerCallbacks();
		}

		public function getPropertyNames():Array/*/<string>/*/
		{
			return JS.mapKeys(propertyNames)
		}

		private function onKeepLastChange():void
		{
			var diff:int = records.length - keepLast.value;
			if (diff > 0)
			{
				records = records.slice(diff);
			}
		}

		/**
		 * The session state maps a column name in dataColumns hash map to a value for its "aggregation" metadata.
		 */
		public const aggregationModes:ILinkableVariable = Weave.linkableChild(this, new LinkableVariable(null, typeofIsObject));
		private function typeofIsObject(value:Object):Boolean
		{
			return typeof value == 'object';
		}

		private var records:Array = [];
		private var propertyNames:Object = new JS.global.Set();

		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!_rootNode)
				_rootNode = new ColumnTreeNode({
					cacheSettings: {"label": false},
					dataSource: this,
					dependency: this,
					data: this,
					"label": getLabel,
					hasChildBranches: false,
					children: function():Array {
						return JS.mapValues(propertyNames).map(
							function (columnName:String, ..._):* {
								var meta:Object = {};
								meta[DATA_COLUMNNAME_META] = columnName;
								return generateHierarchyNode(meta);
							}
						);
					}
				});
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (!metadata)
				return null;

			metadata = getColumnMetadata(metadata[DATA_COLUMNNAME_META]);

			if (!metadata)
				return null;

			return new ColumnTreeNode({
				dataSource: this,
				idFields: [DATA_COLUMNNAME_META],
				data: metadata
			});
		}

		private function getColumnMetadata(dataColumnName:String):Object
		{
			var metadata:Object = {};
			metadata[ColumnMetadata.KEY_TYPE] = keyType.value || DataType.STRING;
			metadata[DATA_COLUMNNAME_META] = dataColumnName;
			metadata[ColumnMetadata.TITLE] = dataColumnName;

			var aggState:Object = aggregationModes.getSessionState();
			var aggregation:String = aggState ? aggState[dataColumnName] : null;
			aggregation = aggregation || Aggregation.LAST; /* Keep the last value by default, this makes more sense in streaming applications. */
			metadata[ColumnMetadata.AGGREGATION] = aggregation;

			if (aggregation != Aggregation.SAME)
				metadata[ColumnMetadata.TITLE] = Weave.lang("{0} ({1})", metadata[ColumnMetadata.TITLE], aggregation);

			return metadata;
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var columnName:String = proxyColumn.getMetadata(DATA_COLUMNNAME_META);

			var keys:Array;
			var data:Array = StandardLib.lodash.map(records, columnName);/*.map(
				function (d:*,i:int,a:Array):*
				{
					return d !== undefined ? d : null;
				}
			);*/

			if (keyProperty.value)
			{
				keys = StandardLib.lodash.map(records, keyProperty.value);
			}
			else
			{
				keys = StandardLib.lodash.range(data.length);
			}

			var metadata:Object = getColumnMetadata(columnName);

			var dataType:String = DataSourceUtils.guessDataType(data);
			metadata[ColumnMetadata.DATA_TYPE] = dataType;
			proxyColumn.setMetadata(metadata);

			DataSourceUtils.initColumn(proxyColumn, keys, data);
		}
	}
}

Fix NPE in WebSocketDataSource.

Fix NPE in WebSocketDataSource.


package
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.events.TextEvent;
    import flash.net.Socket;
    import flash.utils.ByteArray;
    import flash.utils.clearTimeout;
    import flash.utils.setTimeout;

    /* An instance of a client-relay connection. */
    public class ProxyPair extends EventDispatcher
    {
        // Label for log messages.
        public var name:String;

        // Socket to client.
        private var s_c:*;
        private var connect_c:Function;

        // Socket to relay.
        private var s_r:*;
        private var connect_r:Function;

        // Parent swfcat, for UI updates and rate meter.
        private var ui:swfcat;

        // Pending byte read counts for relay and client sockets.
        private var r2c_schedule:Array;
        private var c2r_schedule:Array;
        // Callback id.
        private var flush_id:uint;

        public function log(msg:String):void
        {
            if (name)
                ui.puts(name + ": " + msg)
            else
                ui.puts(msg)
        }

        public function logdebug(msg:String):void
        {
            if (ui.debug)
                log(msg);
        }

        public function set_name(name:String):void
        {
            this.name = name;
        }

        public function ProxyPair(ui:swfcat, s_c:*, connect_c:Function, s_r:*, connect_r:Function)
        {
            this.ui = ui;
            /* s_c is a socket for connecting to the client. connect_c is a
               function that, when called, connects s_c. Likewise for s_r and
               connect_r. */
            this.s_c = s_c;
            this.connect_c = connect_c;
            this.s_r = s_r;
            this.connect_r = connect_r;

            this.c2r_schedule = [];
            this.r2c_schedule = [];
        }

        /* Return a function that shows an error message and closes the other half
           of a communication pair. */
        private function socket_error(message:String, other:*):Function
        {
            return function(e:Event):void {
                if (e is TextEvent)
                    log(message + ": " + (e as TextEvent).text + ".");
                else
                    log(message + ".");
                if (other && other.connected)
                    other.close();
                dispatchEvent(new Event(Event.COMPLETE));
            };
        }

        public function connect():void
        {
            s_r.addEventListener(Event.CONNECT, relay_connected);
            s_r.addEventListener(Event.CLOSE, socket_error("Relay: closed", s_c));
            s_r.addEventListener(IOErrorEvent.IO_ERROR, socket_error("Relay: I/O error", s_c));
            s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, socket_error("Relay: security error", s_c));
            s_r.addEventListener(ProgressEvent.SOCKET_DATA, relay_to_client);

            s_c.addEventListener(Event.CONNECT, client_connected);
            s_c.addEventListener(Event.CLOSE, socket_error("Client: closed", s_r));
            s_c.addEventListener(IOErrorEvent.IO_ERROR, socket_error("Client: I/O error", s_r));
            s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, socket_error("Client: security error", s_r));
            s_c.addEventListener(ProgressEvent.SOCKET_DATA, client_to_relay);

            log("Relay: connecting.");
            connect_r();
            log("Client: connecting.");
            connect_c();
        }

        private function relay_connected(e:Event):void
        {
            log("Relay: connected.");
        }

        private function client_connected(e:Event):void
        {
            log("Client: connected.");
        }

        private function relay_to_client(e:ProgressEvent):void
        {
            r2c_schedule.push(e.bytesLoaded);
            flush();
        }

        private function client_to_relay(e:ProgressEvent):void
        {
            c2r_schedule.push(e.bytesLoaded);
            flush();
        }

        private function transfer_chunk(s_from:*, s_to:*, n:uint,
            label:String):void
        {
            var bytes:ByteArray;

            bytes = new ByteArray();
            s_from.readBytes(bytes, 0, n);
            s_to.writeBytes(bytes);
            s_to.flush();
            ui.rate_limit.update(n);
            logdebug(label + ": read " + bytes.length + ".");
        }

        /* Send as much data as the rate limit currently allows. */
        private function flush():void
        {
            if (flush_id)
                clearTimeout(flush_id);
            flush_id = undefined;

            if (!(s_r.connected && s_c.connected))
                /* Can't do anything until both sockets are connected. */
                return;

            while (!ui.rate_limit.is_limited() &&
                   (r2c_schedule.length > 0 || c2r_schedule.length > 0)) {
                if (r2c_schedule.length > 0)
                    transfer_chunk(s_r, s_c, r2c_schedule.shift(), "Relay");
                if (c2r_schedule.length > 0)
                    transfer_chunk(s_c, s_r, c2r_schedule.shift(), "Client");
            }

            /* Call again when safe, if necessary. */
            if (r2c_schedule.length > 0 || c2r_schedule.length > 0)
                flush_id = setTimeout(flush, ui.rate_limit.when() * 1000);
        }
    }
}

package
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.events.TextEvent;
    import flash.net.Socket;
    import flash.utils.ByteArray;
    import flash.utils.clearTimeout;
    import flash.utils.setTimeout;

    /* An instance of a client-relay connection. */
    public class ProxyPair extends EventDispatcher
    {
        // Label for log messages.
        public var name:String;

        // Socket to client.
        private var s_c:*;
        private var connect_c:Function;

        // Socket to relay.
        private var s_r:*;
        private var connect_r:Function;

        // Parent swfcat, for UI updates and rate meter.
        private var ui:swfcat;

        // Pending byte read counts for relay and client sockets.
        private var r2c_schedule:Array;
        private var c2r_schedule:Array;
        // Callback id.
        private var flush_id:uint;

        public function log(msg:String):void
        {
            if (name)
                ui.puts(name + ": " + msg)
            else
                ui.puts(msg)
        }

        public function logdebug(msg:String):void
        {
            if (ui.debug)
                log(msg);
        }

        public function set_name(name:String):void
        {
            this.name = name;
        }

        public function ProxyPair(ui:swfcat, s_c:*, connect_c:Function, s_r:*, connect_r:Function)
        {
            this.ui = ui;
            /* s_c is a socket for connecting to the client. connect_c is a
               function that, when called, connects s_c. Likewise for s_r and
               connect_r. */
            this.s_c = s_c;
            this.connect_c = connect_c;
            this.s_r = s_r;
            this.connect_r = connect_r;

            this.c2r_schedule = [];
            this.r2c_schedule = [];
        }

        /* Return a function that shows an error message and closes the other half
           of a communication pair. */
        private function socket_error(message:String, other:*):Function
        {
            return function(e:Event):void {
                if (e is TextEvent)
                    log(message + ": " + (e as TextEvent).text + ".");
                else
                    log(message + ".");
                if (other && other.connected)
                    other.close();
                dispatchEvent(new Event(Event.COMPLETE));
            };
        }

        public function connect():void
        {
            s_r.addEventListener(Event.CONNECT, relay_connected);
            s_r.addEventListener(Event.CLOSE, relay_closed);
            s_r.addEventListener(IOErrorEvent.IO_ERROR, socket_error("Relay: I/O error", s_c));
            s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, socket_error("Relay: security error", s_c));
            s_r.addEventListener(ProgressEvent.SOCKET_DATA, relay_to_client);

            s_c.addEventListener(Event.CONNECT, client_connected);
            s_c.addEventListener(Event.CLOSE, client_closed);
            s_c.addEventListener(IOErrorEvent.IO_ERROR, socket_error("Client: I/O error", s_r));
            s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, socket_error("Client: security error", s_r));
            s_c.addEventListener(ProgressEvent.SOCKET_DATA, client_to_relay);

            log("Relay: connecting.");
            connect_r();
            log("Client: connecting.");
            connect_c();
        }

        private function relay_connected(e:Event):void
        {
            log("Relay: connected.");
        }

        private function client_connected(e:Event):void
        {
            log("Client: connected.");
        }

        private function relay_closed(e:Event):void
        {
            log("Relay: closed.");
            flush();
        }

        private function client_closed(e:Event):void
        {
            log("Client: closed.");
            flush();
        }

        private function relay_to_client(e:ProgressEvent):void
        {
            r2c_schedule.push(e.bytesLoaded);
            flush();
        }

        private function client_to_relay(e:ProgressEvent):void
        {
            c2r_schedule.push(e.bytesLoaded);
            flush();
        }

        private function transfer_chunk(s_from:*, s_to:*, n:uint,
            label:String):void
        {
            var bytes:ByteArray;

            bytes = new ByteArray();
            s_from.readBytes(bytes, 0, n);
            s_to.writeBytes(bytes);
            s_to.flush();
            ui.rate_limit.update(n);
            logdebug(label + ": read " + bytes.length + ".");
        }

        /* Send as much data as the rate limit currently allows. */
        private function flush():void
        {
            var busy:Boolean;

            if (flush_id)
                clearTimeout(flush_id);
            flush_id = undefined;

            if (!s_r.connected && !s_c.connected)
                /* Can't do anything while both sockets are disconnected. */
                return;

            busy = true;
            while (busy && !ui.rate_limit.is_limited()) {
                busy = false;
                if (s_c.connected && r2c_schedule.length > 0) {
                    transfer_chunk(s_r, s_c, r2c_schedule.shift(), "Relay");
                    busy = true;
                }
                if (s_r.connected && c2r_schedule.length > 0) {
                    transfer_chunk(s_c, s_r, c2r_schedule.shift(), "Client");
                    busy = true;
                }
            }

            if (!s_r.connected && r2c_schedule.length == 0) {
                log("Client: closing.");
                s_c.close();
            }
            if (!s_c.connected && c2r_schedule.length == 0) {
                log("Relay: closing.");
                s_r.close();
            }

            if (!s_c.connected && !s_r.connected)
                dispatchEvent(new Event(Event.COMPLETE));
            else if (r2c_schedule.length > 0 || c2r_schedule.length > 0)
                flush_id = setTimeout(flush, ui.rate_limit.when() * 1000);
        }
    }
}

Allow relaying data while one end of a ProxyPair is disconnected.

Data could be left stranded when one end became closed after data had
been read but before it had been flushed to the other side.


package fairygui {

	public class GObjectPool {
		private var _pool: Object;
		private var _count: Number = 0;

		public function GObjectPool() {
			this._pool = {};
		}

		public function clear(): void {
			var length1: Number = this._pool.length;
			for (var i1: Number = 0; i1 < length1; i1++) {
				var arr: Array = this._pool[i1];
				var cnt: Number = arr.length;
				for (var i: Number = 0; i < cnt; i++)
					arr[i].dispose();
			}
			this._pool = {};
			this._count = 0;
		}

		public function get count(): Number {
			return this._count;
		}

		public function getObject(url: String): GObject {
			url = UIPackage.normalizeURL(url);
			if(url==null)
				return null;

			var arr: Array = this._pool[url];
			if (arr != null && arr.length>0) {
				this._count--;
				return arr.shift();
			}

			var child: GObject = UIPackage.createObjectFromURL(url);
			return child;
		}

		public function returnObject(obj: GObject): void {
			var url: String = obj.resourceURL;
			if (!url)
				return;

			var arr: Array = this._pool[url];
			if (arr == null) {
				arr = [];
				this._pool[url] = arr;
			}

			this._count++;
			arr.push(obj);
		}
	}
}

package fairygui {

	public class GObjectPool {
		private var _pool: Object;
		private var _count: Number = 0;

		public function GObjectPool() {
			this._pool = {};
		}

		public function clear(): void {
			for (var i1:String in this._pool) {
				var arr: Array = this._pool[i1];
				var cnt: Number = arr.length;
				for (var i: Number = 0; i < cnt; i++)
					arr[i].dispose();
			}
			this._pool = {};
			this._count = 0;
		}

		public function get count(): Number {
			return this._count;
		}

		public function getObject(url: String): GObject {
			url = UIPackage.normalizeURL(url);
			if(url==null)
				return null;

			var arr: Array = this._pool[url];
			if (arr != null && arr.length>0) {
				this._count--;
				return arr.shift();
			}

			var child: GObject = UIPackage.createObjectFromURL(url);
			return child;
		}

		public function returnObject(obj: GObject): void {
			var url: String = obj.resourceURL;
			if (!url)
				return;

			var arr: Array = this._pool[url];
			if (arr == null) {
				arr = [];
				this._pool[url] = arr;
			}

			this._count++;
			arr.push(obj);
		}
	}
}

修正 GObjectPool._pool 清理不掉的问题

修正 GObjectPool._pool 清理不掉的问题


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:54
 */
package com.merlinds.miracle_tool.services {
	import com.merlinds.debug.error;
	import com.merlinds.debug.log;
	import com.merlinds.miracle_tool.views.logger.StatusBar;

	import flash.desktop.NativeProcess;
	import flash.desktop.NativeProcessStartupInfo;
	import flash.events.IOErrorEvent;
	import flash.events.NativeProcessExitEvent;
	import flash.events.ProgressEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;

	public class PublishBuilder {

		private var _workDir:File;
		private var _file:File;
		private var _atf:File;
		private var _output:ByteArray;
		private var _callback:Function;
		private var _minMap:int;

		private var _png2atf:File;
		private var _process:NativeProcess;

		public function PublishBuilder() {
			_png2atf = File.applicationDirectory;
			_png2atf = _png2atf.resolvePath("png2atf.exe");
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		public function createATFFile(png:ByteArray, minMap:int, callback:Function):void{
			_callback = callback;
			_minMap = minMap;
			//create temp dir and place resources there
			_workDir = File.createTempDirectory();

			_file = _workDir.resolvePath("png4atf.png");
			_atf = _workDir.resolvePath("output.atf");
			var stream:FileStream = new FileStream();
			stream.openAsync(_file, FileMode.WRITE);
			stream.writeBytes(png);
			stream.close();
			//execute atf packer
			if(NativeProcess.isSupported)
			{
				this.executePngToAtf();
			}
			else
			{
				var errorText:String = "NativeProcess not supported.";
				error(this, errorText);
				StatusBar.error(errorText);
			}

		}
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function executePngToAtf():void {
			var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
			nativeProcessStartupInfo.executable = _png2atf;
			nativeProcessStartupInfo.workingDirectory =_workDir;
			nativeProcessStartupInfo.arguments =  new <String>[
				"-n", "0," + _minMap, "-i", _file.name, "-o", _atf.name
			];

			trace(nativeProcessStartupInfo.arguments);
			_process = new NativeProcess();
			_process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, this.outputDataHandler);
			_process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, this.errorDataHandler);
			_process.addEventListener(NativeProcessExitEvent.EXIT, this.exitHandler);
			_process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, this.ioErrorHandler);
			_process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, this.ioErrorHandler);
			_process.start(nativeProcessStartupInfo);
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function outputDataHandler(event:ProgressEvent):void {
			log(this, "outputDataHandler", "Got: ", _process.standardOutput.readUTFBytes(_process.standardOutput.bytesAvailable));
		}

		private function errorDataHandler(event:ProgressEvent):void {
			error(this, "errorDataHandler", "ERROR -", _process.standardError.readUTFBytes(_process.standardError.bytesAvailable));
		}

		private function ioErrorHandler(event:IOErrorEvent):void {
			error(this, "errorDataHandler",event.toString());
		}

		private function exitHandler(event:NativeProcessExitEvent):void {
			log(this, "outputDataHandler", "Process exited with ", event.exitCode);
			if(event.exitCode == 0){
				//read parsed atf
				_output = new ByteArray();
				var stream:FileStream = new FileStream();
				stream.open(_atf, FileMode.READ);
				stream.readBytes(_output);
				stream.close();
				trace(_atf.nativePath);
				_atf.deleteFile();
				_file.deleteFile();
				_workDir.deleteDirectory(true);
				if(_callback is Function){
					_callback.apply(this);
				}
			}
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS

		public function get output():ByteArray {
			return _output;
		}

//} endregion GETTERS/SETTERS ==================================================
	}
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:54
 */
package com.merlinds.miracle_tool.services {
	import com.merlinds.debug.error;
	import com.merlinds.debug.log;
	import com.merlinds.miracle_tool.views.logger.StatusBar;

	import flash.desktop.NativeProcess;
	import flash.desktop.NativeProcessStartupInfo;
	import flash.events.IOErrorEvent;
	import flash.events.NativeProcessExitEvent;
	import flash.events.ProgressEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;

	public class PublishBuilder {

		private var _workDir:File;
		private var _file:File;
		private var _atf:File;
		private var _output:ByteArray;
		private var _callback:Function;
		private var _minMap:int;

		private var _png2atf:File;
		private var _process:NativeProcess;

		public function PublishBuilder() {
			_png2atf = File.applicationDirectory;
			_png2atf = _png2atf.resolvePath("png2atf.exe");
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		public function createATFFile(png:ByteArray, minMap:int, callback:Function):void{
			_callback = callback;
			_minMap = minMap;
			//create temp dir and place resources there
			_workDir = File.createTempDirectory();

			_file = _workDir.resolvePath("png4atf.png");
			_atf = _workDir.resolvePath("output.atf");
			var stream:FileStream = new FileStream();
			stream.open(_file, FileMode.WRITE);
			stream.writeBytes(png);
			stream.close();
			//execute atf packer
			if(NativeProcess.isSupported)
			{
				this.executePngToAtf();
			}
			else
			{
				var errorText:String = "NativeProcess not supported.";
				error(this, errorText);
				StatusBar.error(errorText);
			}

		}
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function executePngToAtf():void {
			var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
			nativeProcessStartupInfo.executable = _png2atf;
			nativeProcessStartupInfo.workingDirectory =_workDir;
			nativeProcessStartupInfo.arguments =  new <String>[
				"-n", "0," + _minMap, "-i", _file.name, "-o", _atf.name
			];

			trace(nativeProcessStartupInfo.arguments);
			_process = new NativeProcess();
			_process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, this.outputDataHandler);
			_process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, this.errorDataHandler);
			_process.addEventListener(NativeProcessExitEvent.EXIT, this.exitHandler);
			_process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, this.ioErrorHandler);
			_process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, this.ioErrorHandler);
			_process.start(nativeProcessStartupInfo);
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function outputDataHandler(event:ProgressEvent):void {
			log(this, "outputDataHandler", "Got: ", _process.standardOutput.readUTFBytes(_process.standardOutput.bytesAvailable));
		}

		private function errorDataHandler(event:ProgressEvent):void {
			error(this, "errorDataHandler", "ERROR -", _process.standardError.readUTFBytes(_process.standardError.bytesAvailable));
		}

		private function ioErrorHandler(event:IOErrorEvent):void {
			error(this, "errorDataHandler",event.toString());
		}

		private function exitHandler(event:NativeProcessExitEvent):void {
			log(this, "outputDataHandler", "Process exited with ", event.exitCode);
			if(event.exitCode == 0){
				//read parsed atf
				_output = new ByteArray();
				var stream:FileStream = new FileStream();
				stream.open(_atf, FileMode.READ);
				stream.readBytes(_output);
				stream.close();
				trace(_atf.nativePath);
				_atf.deleteFile();
				_file.deleteFile();
				_workDir.deleteDirectory(true);
				if(_callback is Function){
					_callback.apply(this);
				}
			}
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS

		public function get output():ByteArray {
			return _output;
		}

//} endregion GETTERS/SETTERS ==================================================
	}
}

fix png publishing

fix png publishing


/*
 * FLINT PARTICLE SYSTEM
 * .....................
 *
 * Author: Richard Lord
 * Copyright (c) Richard Lord 2008-2011
 * http://flintparticles.org
 *
 *
 * Licence Agreement
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.flintparticles.common.initializers
{
	import org.flintparticles.common.events.ParticleEvent;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import org.flintparticles.common.emitters.Emitter;
	import org.flintparticles.common.particles.Particle;

	/**
	 * The MouseEventHandlers Initializer sets event handlers to listen for the mouseOver and mouseOut events on each
	 * particle. To use this initializer, you must use a DisplayObjectRenderer, use an InteractiveObject as the image
	 * for each particle, and set the mouseChildren property of the renderer to true.
	 */

	public class MouseEventHandlers extends InitializerBase
	{
		private var _overHandler:Function;
		private var _outHandler:Function;
		private var _upHandler:Function;
		private var _downHandler:Function;
		private var _clickHandler:Function;

		/**
		 * The constructor creates a MassInit initializer for use by
		 * an emitter. To add a MassInit to all particles created by an emitter, use the
		 * emitter's addInitializer method.
		 *
		 * @param mass the mass for particles
		 * initialized by the instance.
		 *
		 * @see org.flintparticles.common.emitters.Emitter#addInitializer()
		 */
		public function MouseEventHandlers()
		{
		}

		/**
		 * The mouseOver event handler.
		 */
		public function get overHandler():Function
		{
			return _overHandler;
		}
		public function set overHandler( value:Function ):void
		{
			_overHandler = value;
		}

		/**
		 * The mouseOut event handler.
		 */
		public function get outHandler():Function
		{
			return _outHandler;
		}
		public function set outHandler( value:Function ):void
		{
			_outHandler = value;
		}

		/**
		 * The mouseUp event handler.
		 */
		public function get upHandler():Function
		{
			return _upHandler;
		}
		public function set upHandler( value:Function ):void
		{
			_upHandler = value;
		}

		/**
		 * The mouseDown event handler.
		 */
		public function get downHandler():Function
		{
			return _downHandler;
		}
		public function set downHandler( value:Function ):void
		{
			_downHandler = value;
		}

		/**
		 * The mouseClick event handler.
		 */
		public function get clickHandler():Function
		{
			return _clickHandler;
		}
		public function set clickHandler( value:Function ):void
		{
			_clickHandler = value;
		}

		/**
		 * Listens for particles to die and removes the mouse event listeners from them when this occurs.
		 */
		override public function addedToEmitter( emitter:Emitter ):void
		{
			emitter.addEventListener( ParticleEvent.PARTICLE_DEAD, removeListeners, false, 0, true );
		}

		/**
		 * Stops listening for particles to die.
		 */
		override public function removedFromEmitter( emitter:Emitter ):void
		{
			emitter.removeEventListener( ParticleEvent.PARTICLE_DEAD, removeListeners );
		}

		private function removeListeners( event:ParticleEvent ):void
		{
			if( event.particle.image is IEventDispatcher )
			{
				var dispatcher:IEventDispatcher = IEventDispatcher( event.particle.image );
				if( _overHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _overHandler );
				}
				if( _outHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _outHandler );
				}
				if( _upHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _upHandler );
				}
				if( _downHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _downHandler );
				}
				if( _clickHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _clickHandler );
				}
			}

		}

		/**
		 * @inheritDoc
		 */
		override public function initialize( emitter:Emitter, particle:Particle ):void
		{
			if( particle.image is IEventDispatcher )
			{
				var dispatcher:IEventDispatcher = IEventDispatcher( particle.image );
				if( _overHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _overHandler, false, 0, true );
				}
				if( _outHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _outHandler, false, 0, true );
				}
				if( _upHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _upHandler, false, 0, true );
				}
				if( _downHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _downHandler, false, 0, true );
				}
				if( _clickHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _clickHandler, false, 0, true );
				}
			}
		}
	}
}

/*
 * FLINT PARTICLE SYSTEM
 * .....................
 *
 * Author: Richard Lord
 * Copyright (c) Richard Lord 2008-2011
 * http://flintparticles.org
 *
 *
 * Licence Agreement
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.flintparticles.common.initializers
{
	import org.flintparticles.common.events.ParticleEvent;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import org.flintparticles.common.emitters.Emitter;
	import org.flintparticles.common.particles.Particle;

	/**
	 * The MouseEventHandlers Initializer sets event handlers to listen for the mouseOver and mouseOut events on each
	 * particle. To use this initializer, you must use a DisplayObjectRenderer, use an InteractiveObject as the image
	 * for each particle, and set the mouseChildren property of the renderer to true.
	 */

	public class MouseEventHandlers extends InitializerBase
	{
		private var _overHandler:Function;
		private var _outHandler:Function;
		private var _upHandler:Function;
		private var _downHandler:Function;
		private var _clickHandler:Function;

		/**
		 * The constructor creates a MouseEventHandlers initializer for use by
		 * an emitter. To add a MouseEventHandlers to all particles created by an emitter, use the
		 * emitter's addInitializer method.
		 *
		 * @see org.flintparticles.common.emitters.Emitter#addInitializer()
		 */
		public function MouseEventHandlers()
		{
		}

		/**
		 * The mouseOver event handler.
		 */
		public function get overHandler():Function
		{
			return _overHandler;
		}
		public function set overHandler( value:Function ):void
		{
			_overHandler = value;
		}

		/**
		 * The mouseOut event handler.
		 */
		public function get outHandler():Function
		{
			return _outHandler;
		}
		public function set outHandler( value:Function ):void
		{
			_outHandler = value;
		}

		/**
		 * The mouseUp event handler.
		 */
		public function get upHandler():Function
		{
			return _upHandler;
		}
		public function set upHandler( value:Function ):void
		{
			_upHandler = value;
		}

		/**
		 * The mouseDown event handler.
		 */
		public function get downHandler():Function
		{
			return _downHandler;
		}
		public function set downHandler( value:Function ):void
		{
			_downHandler = value;
		}

		/**
		 * The mouseClick event handler.
		 */
		public function get clickHandler():Function
		{
			return _clickHandler;
		}
		public function set clickHandler( value:Function ):void
		{
			_clickHandler = value;
		}

		/**
		 * Listens for particles to die and removes the mouse event listeners from them when this occurs.
		 */
		override public function addedToEmitter( emitter:Emitter ):void
		{
			emitter.addEventListener( ParticleEvent.PARTICLE_DEAD, removeListeners, false, 0, true );
		}

		/**
		 * Stops listening for particles to die.
		 */
		override public function removedFromEmitter( emitter:Emitter ):void
		{
			emitter.removeEventListener( ParticleEvent.PARTICLE_DEAD, removeListeners );
		}

		private function removeListeners( event:ParticleEvent ):void
		{
			if( event.particle.image is IEventDispatcher )
			{
				var dispatcher:IEventDispatcher = IEventDispatcher( event.particle.image );
				if( _overHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _overHandler );
				}
				if( _outHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _outHandler );
				}
				if( _upHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _upHandler );
				}
				if( _downHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _downHandler );
				}
				if( _clickHandler != null )
				{
					dispatcher.removeEventListener( MouseEvent.MOUSE_OVER, _clickHandler );
				}
			}

		}

		/**
		 * @inheritDoc
		 */
		override public function initialize( emitter:Emitter, particle:Particle ):void
		{
			if( particle.image is IEventDispatcher )
			{
				var dispatcher:IEventDispatcher = IEventDispatcher( particle.image );
				if( _overHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _overHandler, false, 0, true );
				}
				if( _outHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _outHandler, false, 0, true );
				}
				if( _upHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _upHandler, false, 0, true );
				}
				if( _downHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _downHandler, false, 0, true );
				}
				if( _clickHandler != null )
				{
					dispatcher.addEventListener( MouseEvent.MOUSE_OVER, _clickHandler, false, 0, true );
				}
			}
		}
	}
}

Fix documentation error.

Fix documentation error.


package aerys.minko.scene.controller
{
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;

	import flash.display.BitmapData;
	import flash.utils.Dictionary;

	/**
	 * EnterFrameController are controllers triggered whenever the Scene.enterFrame
	 * signal is executed.
	 *
	 * The best way to
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class EnterFrameController extends AbstractController
	{
		private var _numTargetsInScene	: Dictionary;

        protected function getNumTargetsInScene(scene : Scene) : uint
        {
            return _numTargetsInScene[scene];
        }

		public function EnterFrameController(targetType	: Class = null)
		{
			super(targetType);

            _numTargetsInScene = new Dictionary(true);

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		protected function targetAddedHandler(ctrl		: EnterFrameController,
											  target	: ISceneNode) : void
		{
			if (target.root is Scene)
				targetAddedToSceneHandler(target, target.root as Scene);
			else
				target.addedToScene.add(targetAddedToSceneHandler);
		}

		protected function targetRemovedHandler(ctrl	: EnterFrameController,
												target	: ISceneNode) : void
		{
			if (target.root is Scene)
				target.removedFromScene.remove(targetRemovedFromSceneHandler);
			else
				target.addedToScene.remove(targetAddedToSceneHandler);
		}

		protected function targetAddedToSceneHandler(target	: ISceneNode,
													 scene	: Scene) : void
		{
			if (target.addedToScene.hasCallback(targetAddedToSceneHandler))
				target.addedToScene.remove(targetAddedToSceneHandler);

			target.removedFromScene.add(targetRemovedFromSceneHandler);

            if (!_numTargetsInScene.hasOwnProperty(scene))
            {
                _numTargetsInScene[scene] = 1;
				scene.enterFrame.add(sceneEnterFrameHandler);
            }
            else
                _numTargetsInScene[scene]++;
		}

		protected function targetRemovedFromSceneHandler(target	: ISceneNode,
														 scene	: Scene) : void
		{
			target.removedFromScene.remove(targetRemovedFromSceneHandler);
			target.addedToScene.add(targetAddedToSceneHandler);

            _numTargetsInScene[scene]--;
			if (_numTargetsInScene[scene] == 0)
            {
                delete _numTargetsInScene[scene];
				scene.enterFrame.remove(sceneEnterFrameHandler);
            }
		}

		protected function sceneEnterFrameHandler(scene			: Scene,
												  viewport		: Viewport,
												  destination	: BitmapData,
												  time			: Number) : void
		{
			throw new Error(
				'The method EnterFrameController.sceneEnterFrameHandler must be overriden.'
			);
		}
	}
}

package aerys.minko.scene.controller
{
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;

	import flash.display.BitmapData;
	import flash.utils.Dictionary;

	/**
	 * EnterFrameController are controllers triggered whenever the Scene.enterFrame
	 * signal is executed.
	 *
	 * The best way to
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class EnterFrameController extends AbstractController
	{
		private var _numTargetsInScene	: Dictionary;

        protected function getNumTargetsInScene(scene : Scene) : uint
        {
            return _numTargetsInScene[scene];
        }

		public function EnterFrameController(targetType	: Class = null)
		{
			super(targetType);

            initialize();
		}

        private function initialize() : void
        {
            _numTargetsInScene = new Dictionary(true);

            targetAdded.add(targetAddedHandler);
            targetRemoved.add(targetRemovedHandler);
        }

		protected function targetAddedHandler(ctrl		: EnterFrameController,
											  target	: ISceneNode) : void
		{
            if (target.scene)
                targetAddedToSceneHandler(target, target.scene);

            target.addedToScene.add(targetAddedToSceneHandler);
            target.removedFromScene.add(targetRemovedFromSceneHandler);
		}

		protected function targetRemovedHandler(ctrl	: EnterFrameController,
												target	: ISceneNode) : void
		{
            if (target.scene)
                targetRemovedFromSceneHandler(target, target.scene);

            target.addedToScene.remove(targetAddedToSceneHandler);
            target.removedFromScene.remove(targetRemovedFromSceneHandler);
		}

		protected function targetAddedToSceneHandler(target	: ISceneNode,
													 scene	: Scene) : void
		{
            if (!_numTargetsInScene.hasOwnProperty(scene))
            {
                _numTargetsInScene[scene] = 1;
                scene.enterFrame.add(sceneEnterFrameHandler);
            }
            else
                _numTargetsInScene[scene]++;
		}

		protected function targetRemovedFromSceneHandler(target	: ISceneNode,
														 scene	: Scene) : void
		{
            _numTargetsInScene[scene]--;
            if (_numTargetsInScene[scene] == 0)
            {
                delete _numTargetsInScene[scene];
                scene.enterFrame.remove(sceneEnterFrameHandler);
            }
		}

		protected function sceneEnterFrameHandler(scene			: Scene,
												  viewport		: Viewport,
												  destination	: BitmapData,
												  time			: Number) : void
		{
			throw new Error(
				'The method EnterFrameController.sceneEnterFrameHandler must be overriden.'
			);
		}
	}
}

fix EnterFrameController to avoid listening to the Scene.enterFrame signal multiple times for the same scene


package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCloneable;
import dolly.data.ClassLevelCopyableCloneable;
import dolly.data.PropertyLevelCloneable;
import dolly.data.PropertyLevelCopyableCloneable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class ClonerTest {

	private var classLevelCloneable:ClassLevelCloneable;
	private var classLevelCloneableType:Type;

	private var classLevelCopyableCloneable:ClassLevelCopyableCloneable;
	private var classLevelCopyableCloneableType:Type;

	private var propertyLevelCloneable:PropertyLevelCloneable;
	private var propertyLevelCloneableType:Type;

	private var propertyLevelCopyableCloneable:PropertyLevelCopyableCloneable;
	private var propertyLevelCopyableCloneableType:Type;

	[Before]
	public function before():void {
		classLevelCloneable = new ClassLevelCloneable();
		classLevelCloneable.property1 = "value 1";
		classLevelCloneable.property2 = "value 2";
		classLevelCloneable.property3 = "value 3";
		classLevelCloneable.writableField = "value 4";
		classLevelCloneableType = Type.forInstance(classLevelCloneable);

		classLevelCopyableCloneable = new ClassLevelCopyableCloneable();
		classLevelCopyableCloneable.property1 = "value 1";
		classLevelCopyableCloneable.property2 = "value 2";
		classLevelCopyableCloneable.property3 = "value 3";
		classLevelCopyableCloneable.writableField = "value 4";
		classLevelCopyableCloneableType = Type.forInstance(classLevelCopyableCloneable);

		propertyLevelCloneable = new PropertyLevelCloneable();
		propertyLevelCloneable.property1 = "value 1";
		propertyLevelCloneable.property2 = "value 2";
		propertyLevelCloneable.property3 = "value 3";
		propertyLevelCloneable.writableField = "value 4";
		propertyLevelCloneableType = Type.forInstance(propertyLevelCloneable);

		propertyLevelCopyableCloneable = new PropertyLevelCopyableCloneable();
		propertyLevelCopyableCloneable.property1 = "value 1";
		propertyLevelCopyableCloneable.property2 = "value 2";
		propertyLevelCopyableCloneable.property3 = "value 3";
		propertyLevelCopyableCloneable.writableField = "value 4";
		propertyLevelCopyableCloneableType = Type.forInstance(propertyLevelCopyableCloneable);
	}

	[After]
	public function after():void {
		classLevelCloneable = null;
		classLevelCloneableType = null;

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCloneable = null;
		propertyLevelCloneableType = null;

		propertyLevelCopyableCloneable = null;
		propertyLevelCopyableCloneableType = null;
	}

	[Test]
	public function testGetCloneableFieldsForType():void {
		var cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);

		cloneableFields = Cloner.getCloneableFieldsForType(classLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);

		cloneableFields = Cloner.getCloneableFieldsForType(propertyLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(3, cloneableFields.length);

		cloneableFields = Cloner.getCloneableFieldsForType(propertyLevelCopyableCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testCloneWithClassLevelMetadata():void {
		const clone1:ClassLevelCloneable = Cloner.clone(classLevelCloneable);
		assertNotNull(clone1);
		assertNotNull(clone1.property1);
		assertEquals(clone1.property1, classLevelCloneable.property1);
		assertNotNull(clone1.property2);
		assertEquals(clone1.property2, classLevelCloneable.property2);
		assertNotNull(clone1.property3);
		assertEquals(clone1.property3, classLevelCloneable.property3);
		assertNotNull(clone1.writableField);
		assertEquals(clone1.writableField, classLevelCloneable.writableField);

		const clone2:ClassLevelCopyableCloneable = Cloner.clone(classLevelCopyableCloneable);
		assertNotNull(clone2);
		assertNotNull(clone2.property1);
		assertEquals(clone2.property1, classLevelCloneable.property1);
		assertNotNull(clone2.property2);
		assertEquals(clone2.property2, classLevelCloneable.property2);
		assertNotNull(clone2.property3);
		assertEquals(clone2.property3, classLevelCloneable.property3);
		assertNotNull(clone2.writableField);
		assertEquals(clone2.writableField, classLevelCloneable.writableField);
	}

	[Test]
	public function testCloneWithPropertyLevelMetadata():void {
		const clone1:PropertyLevelCloneable = Cloner.clone(propertyLevelCloneable);
		assertNotNull(clone1);
		assertNull(clone1.property1);
		assertNotNull(clone1.property2);
		assertEquals(clone1.property2, propertyLevelCloneable.property2);
		assertNotNull(clone1.property3);
		assertEquals(clone1.property3, propertyLevelCloneable.property3);
		assertNotNull(clone1.writableField);
		assertEquals(clone1.writableField, propertyLevelCloneable.writableField);

		const clone2:PropertyLevelCopyableCloneable = Cloner.clone(propertyLevelCopyableCloneable);
		assertNotNull(clone2);
		assertNotNull(clone2.property1);
		assertEquals(clone2.property1, propertyLevelCloneable.property1);
		assertNotNull(clone2.property2);
		assertEquals(clone2.property2, propertyLevelCloneable.property2);
		assertNotNull(clone2.property3);
		assertEquals(clone2.property3, propertyLevelCloneable.property3);
		assertNotNull(clone2.writableField);
		assertEquals(clone2.writableField, propertyLevelCloneable.writableField);
	}
}
}

package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCloneable;
import dolly.data.ClassLevelCopyableCloneable;
import dolly.data.PropertyLevelCloneable;
import dolly.data.PropertyLevelCopyableCloneable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class ClonerTest {

	private var classLevelCloneable:ClassLevelCloneable;
	private var classLevelCloneableType:Type;

	private var classLevelCopyableCloneable:ClassLevelCopyableCloneable;
	private var classLevelCopyableCloneableType:Type;

	private var propertyLevelCloneable:PropertyLevelCloneable;
	private var propertyLevelCloneableType:Type;

	private var propertyLevelCopyableCloneable:PropertyLevelCopyableCloneable;
	private var propertyLevelCopyableCloneableType:Type;

	[Before]
	public function before():void {
		classLevelCloneable = new ClassLevelCloneable();
		classLevelCloneable.property1 = "value 1";
		classLevelCloneable.property2 = "value 2";
		classLevelCloneable.property3 = "value 3";
		classLevelCloneable.writableField = "value 4";
		classLevelCloneableType = Type.forInstance(classLevelCloneable);

		classLevelCopyableCloneable = new ClassLevelCopyableCloneable();
		classLevelCopyableCloneable.property1 = "value 1";
		classLevelCopyableCloneable.property2 = "value 2";
		classLevelCopyableCloneable.property3 = "value 3";
		classLevelCopyableCloneable.writableField = "value 4";
		classLevelCopyableCloneableType = Type.forInstance(classLevelCopyableCloneable);

		propertyLevelCloneable = new PropertyLevelCloneable();
		propertyLevelCloneable.property1 = "value 1";
		propertyLevelCloneable.property2 = "value 2";
		propertyLevelCloneable.property3 = "value 3";
		propertyLevelCloneable.writableField = "value 4";
		propertyLevelCloneableType = Type.forInstance(propertyLevelCloneable);

		propertyLevelCopyableCloneable = new PropertyLevelCopyableCloneable();
		propertyLevelCopyableCloneable.property1 = "value 1";
		propertyLevelCopyableCloneable.property2 = "value 2";
		propertyLevelCopyableCloneable.property3 = "value 3";
		propertyLevelCopyableCloneable.writableField = "value 4";
		propertyLevelCopyableCloneableType = Type.forInstance(propertyLevelCopyableCloneable);
	}

	[After]
	public function after():void {
		classLevelCloneable = null;
		classLevelCloneableType = null;

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCloneable = null;
		propertyLevelCloneableType = null;

		propertyLevelCopyableCloneable = null;
		propertyLevelCopyableCloneableType = null;
	}

	[Test]
	public function testGetCloneableFieldsForTypeClassLevelCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypeClassLevelCopyableCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(classLevelCopyableCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypePropertyLevelCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(propertyLevelCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(3, cloneableFields.length);
	}

	[Test]
	public function testGetCloneableFieldsForTypePropertyLevelCopyableCloneable():void {
		const cloneableFields:Vector.<Field> = Cloner.getCloneableFieldsForType(propertyLevelCopyableCloneableType);
		assertNotNull(cloneableFields);
		assertEquals(4, cloneableFields.length);
	}

	[Test]
	public function testWithClassLevelCloneable():void {
		const clone1:ClassLevelCloneable = Cloner.clone(classLevelCloneable);
		assertNotNull(clone1);
		assertNotNull(clone1.property1);
		assertEquals(clone1.property1, classLevelCloneable.property1);
		assertNotNull(clone1.property2);
		assertEquals(clone1.property2, classLevelCloneable.property2);
		assertNotNull(clone1.property3);
		assertEquals(clone1.property3, classLevelCloneable.property3);
		assertNotNull(clone1.writableField);
		assertEquals(clone1.writableField, classLevelCloneable.writableField);

		const clone2:ClassLevelCopyableCloneable = Cloner.clone(classLevelCopyableCloneable);
		assertNotNull(clone2);
		assertNotNull(clone2.property1);
		assertEquals(clone2.property1, classLevelCloneable.property1);
		assertNotNull(clone2.property2);
		assertEquals(clone2.property2, classLevelCloneable.property2);
		assertNotNull(clone2.property3);
		assertEquals(clone2.property3, classLevelCloneable.property3);
		assertNotNull(clone2.writableField);
		assertEquals(clone2.writableField, classLevelCloneable.writableField);
	}

	[Test]
	public function testCloneWithPropertyLevelMetadata():void {
		const clone1:PropertyLevelCloneable = Cloner.clone(propertyLevelCloneable);
		assertNotNull(clone1);
		assertNull(clone1.property1);
		assertNotNull(clone1.property2);
		assertEquals(clone1.property2, propertyLevelCloneable.property2);
		assertNotNull(clone1.property3);
		assertEquals(clone1.property3, propertyLevelCloneable.property3);
		assertNotNull(clone1.writableField);
		assertEquals(clone1.writableField, propertyLevelCloneable.writableField);

		const clone2:PropertyLevelCopyableCloneable = Cloner.clone(propertyLevelCopyableCloneable);
		assertNotNull(clone2);
		assertNotNull(clone2.property1);
		assertEquals(clone2.property1, propertyLevelCloneable.property1);
		assertNotNull(clone2.property2);
		assertEquals(clone2.property2, propertyLevelCloneable.property2);
		assertNotNull(clone2.property3);
		assertEquals(clone2.property3, propertyLevelCloneable.property3);
		assertNotNull(clone2.writableField);
		assertEquals(clone2.writableField, propertyLevelCloneable.writableField);
	}
}
}

Split method testGetCloneableFieldsForType to a few.