Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Регистрация Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > Автокомплит

Как что-то сделать, при этом ничего не делая
Оценить эту запись

Очередной загрузчик. Часть 2.

Запись от gloomyBrain размещена 05.12.2012 в 01:07

В предыдущей части мы закончили на том, что научились делать разные штуки последовательно или параллельно, по приоритетам или нет, но главное - по очереди. В этой части мы сделаем удобный (IMO) загрузчик каких-угодно-классных-штуковин на основе как раз очереди. Приступим.

Давайте начнем с описания сущности, которая умеет загружать наши какие-угодно-классные-штуковины. Для этого мы сделаем базовый интерфейс любого загрузчика IAssetLoader:
Код AS3:
package nq.net.assets.loaders {
 
    import flash.events.IEventDispatcher;
 
    /**
     * Событие удачной загрузки ресурса
     */
    [Event(name = "complete", type = "flash.events.Event")]
 
    /**
     * Событие неудачи при загрузке
     */
    [Event(name = "cancel", type = "flash.events.Event")]
 
    /**
     * Интерфейс для всех загрузчиков ресурсов
     */
    public interface IAssetLoader extends IEventDispatcher {
 
        /**
         * Начать загрузку ресурса
         *
         * @param   url URL-адрес ресурса для загрузки
         */
        function load(url:String):void;
 
        /**
         * Остановить загрузку
         */
        function close():void;
 
        /**
         * Загруженные данные
         */
        function get data():Object;
 
    }
 
}
Как видим, вообще ничего сложного. Наш IAssetLoader должен всего-то навсего уметь начинать загрузку, прерывать ее в любой момент и отдавать загруженные данные. Также, раз уж мы живем в насквозь асинхронном FlashPlayer'е, наш загрузчик должен отправлять соответствующие события (успех/провал загрузки). Проницательные товарищи уже догадались, что для каждого типа ресурса мы будем делать свою реализацию такого загрузчика. Но обэтом чуть позже.

С загрузчиком все ясно, но нам этого мало. Для чего мы, спрашивается, писали очередь в предыдущей статье? Давайте сделаем задание на загрузку каких-угодно-классных-штуковин. Опять же, универсальное.
Код AS3:
package nq.net.assets {
 
    import flash.events.Event;
    import flash.events.EventDispatcher;
 
    import nq.net.assets.loaders.IAssetLoader;
    import nq.net.assets.storage.IAssetBundle;
    import nq.utils.task.IAsyncTask;
 
    internal final class LoadAssetTask extends EventDispatcher implements IAsyncTask {
 
        private var _storage:IAssetBundle;
        private var _loader:IAssetLoader;
        private var _url:String;
        private var _priority:int;
        private var _mimeType:String;
 
        /**
         * @param storage   Хранилище, в которое нужно положить результат
         * @param loader    Загрузчик ресурса
         * @param url       URL-адрес ресурса для загрузки
         */
        public function LoadAssetTask(storage:IAssetBundle, loader:IAssetLoader, url:String, priority:int, mimeType:String) {
 
            super();
 
            if (!storage) throw new ArgumentError("Параметр не должен быть нулевым!");
            if (!loader) throw new ArgumentError("Параметр не должен быть нулевым!");
            if (!url) throw new ArgumentError("Параметр не должен быть нулевым!");
 
            _storage = storage;
            _loader = loader;
            _url = url;
            _priority = priority;
            _mimeType = mimeType;
        }
 
        private function onCancel(event:Event):void {
 
            this.stop();
 
            super.dispatchEvent(new Event(Event.CANCEL));
        }
 
        private function onComplete(event:Event):void {
 
            this.stop();
 
            _storage.addAsset(_loader.data, _url);
 
            super.dispatchEvent(new Event(Event.COMPLETE));
 
        }
 
        public function get url():String {
            return _url;
        }
 
 
        /* INTERFACE nq.utils.task.IAsyncTask */
 
        /**
	 * Остановить выполнение задания
	 */
        public function stop():void {
 
            _loader.removeEventListener(Event.COMPLETE, onComplete);
            _loader.removeEventListener(Event.CANCEL, onCancel);
 
            _loader.close();
 
        }
 
        /**
	 * Проритет задания
	 */
        public function get priority():int {
            return _priority;
        }
 
        public function get mimeType():String
        {
            return _mimeType;
        }
 
        /**
	 * Начать выполнение задания
	 */
        public function start():void {
 
            _loader.addEventListener(Event.COMPLETE, onComplete);
            _loader.addEventListener(Event.CANCEL, onCancel);
            _loader.load(_url);
 
        }
 
    }
 
}
Не фокусируясь пока на деталях, давайте посмотрим что у нас есть:
- реализация интерфейса IAsyncTask (ведь загрузка - асинхронная операция)
- отправка событий об успешном завершении и/или провале загрузки
- метод get mimeType():String

Остановимся пока на последнем. Вообще MIME-типы описаны в нашей любимой занудной википедии, но в данном случае это не имеет значения. Мы можем вместо них использовать вообще любые строки произвольной длины и ширины. Главное - чтобы строка, возвращаемая этим методом, однозначна говорила нам о типе загружаемой штуковины. Да-да, именно благодаря этой строке мы будем выбирать нужный нам IAssetLoader для загрузки. Кстати, именно этим, по сути, и будет заниматься наш навороченный загрузчик.

Итак, очередь у нас есть, загрузчик тоже есть, чего недостает? Правильно, хранилища! Нам же нужно куда-то складывать все то добро, которое мы грузим в несколько потоков. По возможности постараемся сделать так, чтобы ресурсы можно было не только загрузить, но и выгрузить (чтобы лишнюю память не занимали когда не нужны). Довольно слов, будем выражать мысли кодом. Интерфейс хранилища ресурсов:
Код AS3:
package nq.net.assets.storage {
 
    /**
     * Интерфейс для хранилища загружаемых ресурсов
     */
    public interface IAssetBundle {
 
        /**
         * Добавить ассет в пакет ресурсов
         *
         * @param asset Добавляемый ассет
         * @param uri   Уникальное имя ассета в этом пакете
         */
        function addAsset(asset:Object, uri:String):void;
 
        /**
         * Имеется ли ассет с таким именем в пакете
         *
         * @param uri   Уникальное имя ассета в этом пакете
         */
        function hasAsset(uri:String):Boolean;
 
        /**
         * Удалить ассет по имени
         *
         * @param uri   Уникальное имя ассета в этом пакете
         */
        function removeAsset(uri:String):void;
 
        /**
         * Получить ассет по имени
         *
         * @param uri   Уникальное имя ассета в этом пакете
         */
        function getAsset(uri:String):Object;
 
    }
 
}
Однако, какое-то особенное хранение ресурсов - большая редкость, так что в 99% случаев нам хватит самой обычной реализации, приводимой ниже:
Код AS3:
package nq.net.assets.storage {
 
    /**
     * Класс хранилища загружаемых ресурсов
     */
    public final class AssetBundle implements IAssetBundle {
 
        private var _hash:Object;
 
        public function AssetBundle() {
 
            super();
 
            _hash = new Object();
        }
 
 
        /* INTERFACE nq.net.assets.IAssetBundle */
 
        /**
         * Добавить ассет в пакет ресурсов
         *
         * @param asset Добавляемый ассет
         * @param uri   Уникальное имя ассета в этом пакете
         */
        public function addAsset(asset:Object, uri:String):void {
 
            if (uri in _hash) throw new ArgumentError("Ассет с URI '"+uri+"' уже зарегистрирован!");
 
            _hash[uri] = asset;
        }
 
        /**
         * Имеется ли ассет с таким именем в пакете
         *
         * @param uri   Уникальное имя ассета в этом пакете
         */
        public function hasAsset(uri:String):Boolean {
 
            return (uri in _hash);
        }
 
        /**
         * Удалить ассет по имени
         *
         * @param uri   Уникальное имя ассета в этом пакете
         */
        public function removeAsset(uri:String):void {
 
            if (!(uri in _hash)) throw new ArgumentError("Ассет с таким URI не зарегистрирован!");
 
            delete _hash[uri];
        }
 
        /**
         * Получить ассет по имени
         *
         * @param uri   Уникальное имя ассета в этом пакете
         */
        public function getAsset(uri:String):Object {
 
            if (!(uri in _hash)) throw new ArgumentError("Ассет с таким URI ("+uri+") не зарегистрирован!");
 
            return _hash[uri];
        }
 
    }
 
}
Ну что, теперь вроде бы все. Или не все? Давайте еще раз по пунктам:
- есть очередь, которая может выполнять любые задания последовательно и параллельно (TaskQueue)
- есть интерфейс загрузчика каких-угодно-классных-штуковин
- есть хранилище для штуковин, которые мы уже загрузили
- есть универсальное задание, которое берет загрузчик, загружает штуковину и складывает ее в хранилище

Чего нет? Нет поставщика загрузчиков. Помните, мы говорили, что волшебная строка с MIME-типом поможет нам выбрать загрузчик? Так вот, настало время написать фабрику, которая будет принимать строку с типом и в ответ возвращать нам загрузчик для этого типа. Это удобно и гибко - у нас всегда будет одно место, в котором указаны соответствия между типами и загрузчиками. Фабрика будет выглядеть примерно вот так:
Код AS3:
package nq.net.assets.loaders {
 
    /**
     * Интерфейс для поставщика загрузчиков ресурсов
     */
    public interface IAssetLoaderFactory {
 
        /**
         * Получить загрузчик по типу загружаемого ресурса
         *
         * @param url       URL-адрес загружаемого ресурса
         * @param mimeType  Тип MIME загружаемого ресурса
         */
        function getLoader(url:String, mimeType:String = "application/octet-stream"):IAssetLoader;
 
    }
 
}
Да, да, опять интерфейс. Ну а что делать, если мы хотим все сделать максимально реиспользуемым.

Все, я устал и мне надоело. Это вторая статья за сегодня, так что пример 2 загрузчиков, пример фабрики и пример использования - во вложениях. Хватит с меня. А с вас - восхваления. Можно орден. Но лучше лексус, нафига мне орден, правильно?

PS
Буду рад пояснить что-либо в комментариях если кто-то дочитал этот пост но не разобрался как пользоваться.
Вложения
Тип файла: zip src.zip (32.9 Кб, 155 просмотров)
Всего комментариев 9

Комментарии

Старый 05.12.2012 03:54 alatar вне форума
alatar
 
Аватар для alatar
Complete есть, cancel есть, а где fault? Или предполагается, что все работает идеально?
Старый 05.12.2012 10:57 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Предполагается что cancel - это "не получилось". Если мы сами отменяем загрузку, то мы знаем почему не получилось. Если не сами - то тоже знаем (методом исключения).
Старый 05.12.2012 19:36 alatar вне форума
alatar
 
Аватар для alatar
А пользователю что показывать? "Видимо что-то случилось"?
Старый 05.12.2012 20:08 elder_Nosferatu вне форума
elder_Nosferatu
 
Аватар для elder_Nosferatu
@alatar
Цитата:
Если мы сами отменяем загрузку, то мы знаем почему не получилось. Если не сами - то тоже знаем (методом исключения)
Самостоятельная отмена действия должна иметь какое нибуть обоснование, которое, при желании/набобности, можна изложить пользователю, а непредвиденная отмена - следствие ошибок или неучтенных факторов. Скорее всего пользователь с этим ничего поделать не сможет, а потому и нет смысла в развернутом виде ему излагать суть проблемы, хватит и банального "Упс..."
Старый 05.12.2012 22:48 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
CANCEL - это то, что отправляется "наружу". Это минимальная информация и она больше нужна очереди, чтобы та могла продолжить работу. Любое детальное логирование нужно делать непосредственно внутри задания и отдельными средствами, IMO.
В данном случае выбор подхода обоснован еще и тем, что по сути я даже не знаю, загружаю ли я что-либо. Внутри реализации загрузчика (IAssetLoader) может происходить вообще что угодно. В том числе и НЕзагрузка. Как и что там может происходить во всех деталях - предусмотреть невозможно. Так что "получилось" и "не получилось" - вполне логичное разделение.

@alatar
А что вы обчно показываете пользователям? Ошибка #xxxx в строке yyyy? По-моему "что-то сломалось" куда более информативно.
Старый 06.12.2012 15:04 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
Поглядел.
Стопицот интерфейсов вгоняют в уныние.
Плюс к тому же я бы выбор лоадера по типу всунул внутрь манагера (и глобально пофигу выбирать лоадер по типу или по расширению, а можно два словаря - расширение:майм + майм:лоадер).
Та и проверку загружен ли такой объект тоже можно как-то попробовать.
Фабрику лоадеров пользователю показывать тоже необязательно, внутрь манагера ее всунуть и всё.
Цепочка наследования IAssetMansger<-IAssetBundle ни к чему совершенно, достаточно одного манагера, тем более, разница только в том что манагер умеет грузить а бандл нет. (в крайнем случае в манагер всунуть бандл композицией, логически точнее будет имхо).

Идея норм, реализация на низком уровне норм, но на высоком уровне логика нифига не прозрачна, пользоваться неудобно.
Старый 06.12.2012 20:45 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Цитата:
Поглядел.
Выбор лоадера осуществляется (может осуществляться) по паре url + mime, поэтому и сделана возможность делать свою фабрику.
Проверка загружен ли объект - AssetManager.hasAsset()
Цепочка наследования ни к чему не обязывает, так что...

Вообще, говоря о прозрачности для пользователя, могу сказать что ему нужно сделать всего 2 вещи:
1) - написать свои лоадеры для загрузки (или взять готовые, потому что они на 100% остаются от проекта к проекту)
2) - написать свою фабрику, которая эти лоадеры возвращает (или взять готовую, по тем же причинам)

Потом просто передать фабрику в менеджер и наслаждаться. То есть в худшем случае нужно реализовать 2 интерфейса, и только 1 из них несколько раз.
Старый 07.12.2012 07:04 Dukobpa3 вне форума
Dukobpa3
 
Аватар для Dukobpa3
Цитата:
Выбор лоадера осуществляется (может осуществляться) по паре url + mime, поэтому и сделана возможность делать свою фабрику.
Зачем мне как пользователю системы вообще знать что такое фабрика лоадеров?

Если я напишу свои лоадеры и свою фабрику - то зачем мне эта система? Она кроме этого умеет что-то уникальное что нельзя написать строк в 50-100 кода за два часа?
Старый 07.12.2012 10:46 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Цитата:
Зачем мне как пользователю системы вообще знать что такое фабрика лоадеров?
Простите, видимо я неверно выбрал ресурс для изложения мыслей. Мне казалось, что большинство людей тут умеют читать.
 

 


Часовой пояс GMT +4, время: 20:28.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.