/* CASA Framework for ActionScript 3.0 Copyright (c) 2008, Contributors of CASA Framework All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the CASA Framework nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.casaframework.load { import flash.events.IOErrorEvent; import org.casaframework.events.LoadEvent; import org.casaframework.load.BaseLoadItem; import org.casaframework.math.Percent; import org.casaframework.util.ArrayUtil; [Event(name="complete", type="org.casaframework.events.LoadEvent")] [Event(name="ioError", type="flash.events.IOErrorEvent")] [Event(name="progress", type="org.casaframework.events.LoadEvent")] [Event(name="start", type="org.casaframework.events.LoadEvent")] [Event(name="stop", type="org.casaframework.events.LoadEvent")] /** Allows multiple loads to be grouped and treated as one larger load. @author Aaron Clinger @version 04/06/08 @example package { import flash.display.MovieClip; import org.casaframework.load.GraphicLoad; import org.casaframework.load.GroupLoad; import org.casaframework.events.LoadEvent; public class MyExample extends MovieClip { protected var _groupLoad:GroupLoad; protected var _imageOne:GraphicLoad; protected var _imageTwo:GraphicLoad; protected var _imageThree:GraphicLoad; protected var _imageFour:GraphicLoad; public function MyExample() { super(); this._imageOne = new GraphicLoad("test1.jpg"); this._imageTwo = new GraphicLoad("test2.jpg"); this._imageThree = new GraphicLoad("test3.jpg"); this._imageFour = new GraphicLoad("test4.jpg"); this._groupLoad = new GroupLoad(); this._groupLoad.addLoad(this._imageOne); this._groupLoad.addLoad(this._imageTwo); this._groupLoad.addLoad(this._imageThree); this._groupLoad.addLoad(this._imageFour); this._groupLoad.addEventListener(LoadEvent.PROGRESS, this._onProgress); this._groupLoad.addEventListener(LoadEvent.COMPLETE, this._onComplete); this._groupLoad.start(); } protected function _onProgress(e:LoadEvent):void { trace("Group is " + e.progress.percentage + "% loaded at " + e.Bps + "Bps."); } protected function _onComplete(e:LoadEvent):void { trace("Group has loaded."); } } } */ public class GroupLoad extends BaseLoadItem { protected var _Bps:int = -1; protected var _loaded:Boolean; protected var _loading:Boolean; protected var _loads:Array = new Array(); protected var _preventCache:Boolean; protected var _progress:Percent = new Percent(); protected var _threads:uint; /** Created a new GroupLoad. @param theads: The number of threads the class will theoretically use, though most browsers cap the amount of threads and hold the other requests in a queue. Pass {@code 0} for unlimited threads. */ public function GroupLoad(threads:uint = 2) { super(); this._threads = threads; } /** Add a load to the group. @param load: Load to be added to the group. Can be any class that extends from {@link BaseLoadItem} (Including another GroupLoad) and dispatches events {@link LoadEvent#COMPLETE}, {@link LoadEvent#PROGRESS} and {@code IOErrorEvent}. @param percentOfGroup: Defines the percentage of the total group the size of the load item represents; defaults to equal increments. */ public function addLoad(load:BaseLoadItem, percentOfGroup:Percent = null):void { if (this.loading) throw new Error('Cannot modify GroupLoad while loading.'); this.removeLoad(load); this._loads.push(new GroupLoadItem(load, percentOfGroup)); } /** Removes a load item from the group. @param load: Load to be removed from the group. */ public function removeLoad(load:BaseLoadItem):void { if (this.loading) throw new Error('Cannot modify GroupLoad while loading.'); var l:uint = this._loads.length; while (l--) if (this._loads[l].loadItem == load) this._loads.splice(l, 1); } /** Starts the load process. */ override public function start():void { if (this._loading) return; this._loading = true; this._loaded = false; this._progress = new Percent(); this._Bps = -1; this._checkTotalPercentValidity(); this.dispatchEvent(this._createDefinedLoadEvent(LoadEvent.START)); this._checkQueue(); } /** Stops the load process and stops any currently loading items. */ override public function stop():void { if (!this.loading) return; this._stop(); this.dispatchEvent(this._createDefinedLoadEvent(LoadEvent.STOP)); } /** The loads that compose the group. @return Returns an Array containing all loads in the group. */ public function get loads():Array { var loadItems:Array = new Array(); var l:uint = this._loads.length; while (l--) loadItems.push(this._loads.loadItem); return loadItems; } /** Specifies if a random value name/value pair should be appended to every load in GroupLoad {@code true}, or not append {@code false}; defaults to {@code false}. @see LoadItem#preventCache */ override public function get preventCache():Boolean { return this._preventCache; } override public function set preventCache(cache:Boolean):void { this._preventCache = cache; } /** The percent that the group is loaded. */ override public function get progress():Percent { return this._progress.clone(); } /** Determines if the group is loading {@code true}, or if it isn't currently loading {@code false}. */ override public function get loading():Boolean { return this._loading; } /** Determines if all loads in the group are loaded {@code true}, or if the group hasn't finished loading {@code false}. */ override public function get loaded():Boolean { return this._loaded; } /** The current download speed of the group in bytes per second. */ override public function get Bps():int { return this._Bps; } protected function _stop():void { var l:uint = this._loads.length; while (l--) { if (this._loads[l].loadItem.loading) { this._removeListeners(this._loads[l].loadItem); this._loads[l].loadItem.stop(); } } this._loading = false; } protected function _checkQueue():void { if (!this.loading) return; var l:int = this._loads.length; var t:int = (this._threads == 0) ? l : this._threads; var item:BaseLoadItem; var hasCompleted:Boolean = true; var a:uint; while (l--) if (this._loads[l].loadItem.loading) a++; if (a >= t) return; t -= a; l = -1; while (++l < this._loads.length) { item = this._loads[l].loadItem; if (item.loading || !item.loaded) hasCompleted = false; if (!item.loading && !item.loaded) { if (t-- > 0) { item.addEventListener(IOErrorEvent.IO_ERROR, this._onLoadError, false, 0, true); item.addEventListener(LoadEvent.COMPLETE, this._onComplete, false, 0, true); item.addEventListener(LoadEvent.PROGRESS, this._onProgress, false, 0, true); if (this.preventCache) item.preventCache = true; item.start(); } } } if (hasCompleted) this._onGroupComplete(); } protected function _checkTotalPercentValidity():void { var l:uint = this._loads.length; var perTotal:Number = 0; while (l--) perTotal += this._loads[l].percent.decimalPercentage; if (perTotal != 1) { l = this._loads.length; while (l--) this._loads[l].percent = new Percent(this._loads[l].percent.decimalPercentage / perTotal); } } protected function _onProgress(e:LoadEvent):void { this._calculateProgess(); this.dispatchEvent(this._createDefinedLoadEvent(LoadEvent.PROGRESS)); } protected function _onComplete(e:LoadEvent):void { this._removeListeners(e.target as BaseLoadItem); this._checkQueue(); } protected function _onLoadError(e:IOErrorEvent):void { this._removeListeners(e.target as BaseLoadItem); this._stop(); this.dispatchEvent(e); } protected function _onGroupComplete():void { this._stop(); this._calculateProgess(); this._loaded = true; this.dispatchEvent(this._createDefinedLoadEvent(LoadEvent.COMPLETE)); } protected function _calculateProgess():void { var speed:Array = new Array(); var perTotal:Number = 0; var l:uint = this._loads.length; var item:BaseLoadItem; while (l--) { item = this._loads[l].loadItem; if (item.loading) { speed.push(item.Bps); perTotal += this._loads[l].percent.decimalPercentage * item.progress.decimalPercentage; } else if (item.loaded) { speed.push(item.Bps); perTotal += this._loads[l].percent.decimalPercentage; } } if (speed.length != 0) this._Bps = int(ArrayUtil.average(speed)); else this._Bps = -1; this._progress = new Percent(perTotal); } protected function _createDefinedLoadEvent(type:String):LoadEvent { var loadEvent:LoadEvent = new LoadEvent(type); loadEvent.bytesLoaded = 0; loadEvent.bytesTotal = 0; loadEvent.progress = this.progress; loadEvent.Bps = this.Bps; return loadEvent; } protected function _removeListeners(load:BaseLoadItem):void { load.removeEventListener(IOErrorEvent.IO_ERROR, this._onLoadError); load.removeEventListener(LoadEvent.COMPLETE, this._onComplete); load.removeEventListener(LoadEvent.PROGRESS, this._onProgress); } } } import org.casaframework.load.BaseLoadItem; import org.casaframework.math.Percent; class GroupLoadItem { protected var _loadItem:BaseLoadItem; protected var _percent:Percent; public function GroupLoadItem(load:BaseLoadItem, per:Percent) { this.loadItem = load; this.percent = per; } public function get loadItem():BaseLoadItem { return this._loadItem; } public function set loadItem(load:BaseLoadItem):void { this._loadItem = load; } public function get percent():Percent { return this._percent.clone(); } public function set percent(per:Percent):void { if (per == null || per.percentage <= 0) this._percent = new Percent(0.01); else this._percent = per.clone(); } }