Я бы начал с того, что не думал о менеджере активов . Размышления о вашей архитектуре в произвольно определенных терминах (например, «менеджер») имеют тенденцию позволять вам мысленно скрывать многие детали под ковриком, и, следовательно, становится труднее найти решение.
Сосредоточьтесь на своих конкретных потребностях, которые, как представляется, связаны с созданием механизма загрузки ресурсов, который абстрагирует базовое хранилище источника и обеспечивает расширяемость поддерживаемого набора типов. На самом деле в вашем вопросе нет ничего такого, например, как кеширование уже загруженных ресурсов - это хорошо, потому что в соответствии с принципом единой ответственности, вы, вероятно, должны построить кэш ресурсов как отдельную сущность и объединить два интерфейса в другом месте. при необходимости.
Чтобы решить вашу конкретную проблему, вы должны спроектировать загрузчик так, чтобы он не выполнял загрузку каких-либо активов сам, а скорее делегировал эту ответственность интерфейсам, предназначенным для загрузки определенных типов ресурсов. Например:
interface ITypeLoader {
object Load (Stream assetStream);
}
Вы можете создавать новые классы, которые реализуют этот интерфейс, при этом каждый новый класс настраивается для загрузки определенного типа данных из потока. Используя поток, загрузчик типов может быть записан с использованием общего интерфейса, не зависящего от хранилища, и его не нужно жестко кодировать для загрузки с диска или из базы данных; это даже позволит вам загружать ваши ресурсы из сетевых потоков (что может быть очень полезно при выполнении горячей перезагрузки ресурсов, когда ваша игра запущена на консоли, а инструменты редактирования - на компьютере, подключенном к сети).
Ваш основной загрузчик ресурсов должен иметь возможность регистрировать и отслеживать следующие типовые загрузчики:
class AssetLoader {
public void RegisterType (string key, ITypeLoader loader) {
loaders[key] = loader;
}
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
Используемый здесь «ключ» может быть любым, и вам не обязательно должна быть строка, но с нее легко начать. Ключ будет влиять на то, как вы ожидаете, что пользователь идентифицирует конкретный актив, и будет использоваться для поиска соответствующего загрузчика. Поскольку вы хотите скрыть тот факт, что реализация может использовать файловую систему или базу данных, у вас не может быть пользователей, обращающихся к ресурсам по пути файловой системы или чему-либо подобному.
Пользователи должны ссылаться на актив с минимальным количеством информации. В некоторых случаях достаточно одного только имени файла, но я обнаружил, что часто желательно использовать пару тип / имя, чтобы все было очень явно. Таким образом, пользователь может ссылаться на именованный экземпляр одного из ваших файлов анимации XML как "AnimationXml","PlayerWalkCycle"
.
Здесь AnimationXml
будет ключ, под которым вы зарегистрированы AnimationXmlLoader
, который реализует IAssetLoader
. Очевидно, PlayerWalkCycle
идентифицирует конкретный актив. По имени типа и имени ресурса ваш загрузчик ресурсов может запросить в своем постоянном хранилище необработанные байты этого ресурса. Поскольку здесь мы стремимся к максимальной общности, вы можете реализовать это, передав загрузчику средство доступа к хранилищу при его создании, позволяя вам заменить носитель на что-нибудь, что позже может предоставить поток:
interface IAssetStreamProvider {
Stream GetStream (string type, string name);
}
class AssetLoader {
public AssetLoader (IAssetStreamProvider streamProvider) {
provider = streamProvider;
}
object LoadAsset (string type, string name) {
var loader = loaders[type];
var stream = provider.GetStream(type, name);
return loader.Load(stream);
}
public void RegisterType (string type, ITypeLoader loader) {
loaders[type] = loader;
}
IAssetStreamProvider provider;
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
Очень простой поставщик потока просто ищет в указанном корневом каталоге ресурсов подкаталог с именем, type
загружает необработанные байты указанного файла name
в поток и возвращает его.
Короче говоря, у вас есть система, в которой:
- Есть класс, который знает, как читать необработанные байты из некоторого внутреннего хранилища (диск, база данных, сетевой поток и т. Д.).
- Есть классы, которые знают, как превратить необработанный поток байтов в определенный тип ресурса и возвращать его.
- У вашего фактического «загрузчика ресурсов» просто есть набор вышеупомянутых и он знает, как передать выходные данные поставщика потоков в загрузчик для конкретного типа и, таким образом, создать конкретный ресурс. Предоставляя способы настройки поставщика потоков и загрузчиков для конкретного типа, вы получаете систему, которая может быть расширена клиентами (или вами) без необходимости изменения фактического кода загрузчика ресурсов.
Некоторые предостережения и заключительные замечания:
Приведенный выше код в основном на C #, но должен переводиться практически на любой язык с минимальными усилиями. Чтобы облегчить это, я пропустил много вещей, таких как проверка ошибок или правильное использование, IDisposable
и другие идиомы, которые могут не применяться напрямую в других языках. Те оставлены в качестве домашней работы для читателя.
Точно так же я возвращаю конкретный актив, как object
указано выше, но вы можете использовать шаблоны или шаблоны или что-то еще, чтобы создать более конкретный тип объекта, если вам нравится (вам следует, с ним приятно работать).
Как и выше, здесь я вообще не имею дело с кэшированием. Тем не менее, вы можете добавить кеширование легко и с той же универсальностью и настраиваемостью. Попробуйте и посмотрите!
Есть много-много-много способов сделать это, и, конечно, нет единого пути или единого мнения, поэтому вы не смогли его найти. Я попытался предоставить достаточно кода, чтобы объяснить конкретные моменты, не превращая этот ответ в мучительно длинную стену кода. Это уже чрезвычайно долго, как это. Если у вас есть уточняющие вопросы, не стесняйтесь комментировать или найти меня в чате .