Я бы начал с того, что не думал о менеджере активов . Размышления о вашей архитектуре в произвольно определенных терминах (например, «менеджер») имеют тенденцию позволять вам мысленно скрывать многие детали под ковриком, и, следовательно, становится труднее найти решение.
Сосредоточьтесь на своих конкретных потребностях, которые, как представляется, связаны с созданием механизма загрузки ресурсов, который абстрагирует базовое хранилище источника и обеспечивает расширяемость поддерживаемого набора типов. На самом деле в вашем вопросе нет ничего такого, например, как кеширование уже загруженных ресурсов - это хорошо, потому что в соответствии с принципом единой ответственности, вы, вероятно, должны построить кэш ресурсов как отдельную сущность и объединить два интерфейса в другом месте. при необходимости.
Чтобы решить вашу конкретную проблему, вы должны спроектировать загрузчик так, чтобы он не выполнял загрузку каких-либо активов сам, а скорее делегировал эту ответственность интерфейсам, предназначенным для загрузки определенных типов ресурсов. Например:
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указано выше, но вы можете использовать шаблоны или шаблоны или что-то еще, чтобы создать более конкретный тип объекта, если вам нравится (вам следует, с ним приятно работать).
Как и выше, здесь я вообще не имею дело с кэшированием. Тем не менее, вы можете добавить кеширование легко и с той же универсальностью и настраиваемостью. Попробуйте и посмотрите!
Есть много-много-много способов сделать это, и, конечно, нет единого пути или единого мнения, поэтому вы не смогли его найти. Я попытался предоставить достаточно кода, чтобы объяснить конкретные моменты, не превращая этот ответ в мучительно длинную стену кода. Это уже чрезвычайно долго, как это. Если у вас есть уточняющие вопросы, не стесняйтесь комментировать или найти меня в чате .