Je commencerais par ne pas penser à un gestionnaire d' actifs . Penser votre architecture dans des termes vaguement définis (comme «gestionnaire») a tendance à vous laisser glisser mentalement de nombreux détails sous le tapis, et par conséquent, il devient plus difficile de trouver une solution.
Concentrez-vous sur vos besoins spécifiques, ce qui semble être lié à la création d'un mécanisme de chargement des ressources qui résume le stockage d'origine sous-jacent et permet l'extensibilité de l'ensemble de types pris en charge. Il n'y a vraiment rien dans votre question concernant, par exemple, la mise en cache des ressources déjà chargées - ce qui est bien, car conformément à la principe de responsabilité unique, vous devriez probablement créer un cache d'actifs en tant qu'entité distincte et agréger les deux interfaces ailleurs , selon le cas.
Pour répondre à votre préoccupation spécifique, vous devez concevoir votre chargeur de sorte qu'il ne fasse pas le chargement des actifs lui-même, mais délègue plutôt cette responsabilité à des interfaces adaptées au chargement de types spécifiques d'actifs. Par exemple:
interface ITypeLoader {
object Load (Stream assetStream);
}
Vous pouvez créer de nouvelles classes qui implémentent cette interface, chaque nouvelle classe étant adaptée au chargement d'un type spécifique de données à partir d'un flux. En utilisant un flux, le chargeur de type peut être écrit sur une interface commune, indépendante du stockage, et n'a pas besoin d'être codé en dur pour charger à partir du disque ou d'une base de données; cela vous permettrait même de charger vos actifs à partir de flux réseau (ce qui peut être très utile pour implémenter le rechargement à chaud des actifs lorsque votre jeu s'exécute sur une console et vos outils d'édition sur un PC connecté au réseau).
Votre chargeur d'actifs principal doit pouvoir enregistrer et suivre ces chargeurs spécifiques au type:
class AssetLoader {
public void RegisterType (string key, ITypeLoader loader) {
loaders[key] = loader;
}
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
La "clé" utilisée ici peut être ce que vous voulez - et ce n'est pas nécessairement une chaîne, mais celles-ci sont faciles à démarrer. La clé tiendra compte de la façon dont vous vous attendez à ce qu'un utilisateur identifie un actif particulier et sera utilisée pour rechercher le chargeur approprié. Parce que vous voulez masquer le fait que l'implémentation utilise peut-être un système de fichiers ou une base de données, vous ne pouvez pas avoir d'utilisateurs référençant des actifs par un chemin de système de fichiers ou quelque chose comme ça.
Les utilisateurs doivent se référer à un actif avec un strict minimum d'informations. Dans certains cas, un seul nom de fichier suffirait à lui seul, mais j'ai constaté qu'il est souvent souhaitable d'utiliser une paire type / nom, donc tout est très explicite. Ainsi, un utilisateur peut se référer à une instance nommée de l'un de vos fichiers XML d'animation comme "AnimationXml","PlayerWalkCycle"
.
Ici, ce AnimationXml
serait la clé sous laquelle vous vous êtes inscrit AnimationXmlLoader
, qui implémente IAssetLoader
. De toute évidence, PlayerWalkCycle
identifie l'actif spécifique. Étant donné un nom de type et un nom de ressource, votre chargeur d'actifs peut interroger son stockage persistant pour les octets bruts de cet actif. Puisque nous recherchons ici une généralité maximale, vous pouvez l'implémenter en transmettant au chargeur un moyen d'accès au stockage lorsque vous le créez, vous permettant de remplacer le support de stockage par tout ce qui peut fournir un flux plus tard:
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>();
}
Un fournisseur de flux très simple rechercherait simplement dans un répertoire racine d'actif spécifié un sous-répertoire nommé type
et chargerait les octets bruts du fichier nomméname
dans un flux et le retournerait.
En bref, ce que vous avez ici est un système où:
- Il existe une classe qui sait lire les octets bruts d'une sorte de stockage backend (un disque, une base de données, un flux réseau, etc.).
- Il existe des classes qui savent comment transformer un flux d'octets bruts en un type spécifique de ressource et le renvoyer.
- Votre "chargeur d'actifs" réel a juste une collection de ce qui précède et sait comment diriger la sortie du fournisseur de flux dans le chargeur spécifique au type et ainsi produire un actif concret. En exposant les moyens de configurer le fournisseur de flux et les chargeurs spécifiques au type, vous disposez d'un système qui peut être étendu par les clients (ou vous-même) sans avoir à modifier le code du chargeur d'actifs réel.
Quelques mises en garde et notes finales:
Le code ci-dessus est essentiellement C #, mais devrait se traduire dans à peu près n'importe quel langage avec un minimum d'effort. Pour faciliter cela, j'ai omis beaucoup de choses comme la vérification des erreurs ou l'utilisation correcte IDisposable
et d'autres idiomes qui peuvent ne pas s'appliquer directement dans d'autres langues. Ceux-ci sont laissés comme devoirs pour le lecteur.
De même, je retourne l'actif concret comme object
ci-dessus, mais vous pouvez utiliser des génériques ou des modèles ou autre pour produire un type d'objet plus spécifique si vous le souhaitez (vous devriez, c'est agréable de travailler avec).
Comme ci-dessus, je ne traite pas du tout de la mise en cache ici. Cependant, vous pouvez ajouter la mise en cache facilement et avec le même type de généralité et de configurabilité. Essayez-le et voyez!
Il y a beaucoup, beaucoup et beaucoup de façons de le faire, et il n'y a certainement pas de voie unique ou de consensus, c'est pourquoi vous n'avez pas pu en trouver une. J'ai essayé de fournir suffisamment de code pour faire passer les points spécifiques sans transformer cette réponse en un mur de code douloureusement long. C'est déjà extrêmement long. Si vous avez des questions à clarifier, n'hésitez pas à commenter ou à me retrouver dans le chat .