Vorrei iniziare senza pensare a un gestore patrimoniale . Pensare alla tua architettura in termini vagamente definiti (come "manager") tende a permetterti di spazzare mentalmente molti dettagli sotto il tappeto, e di conseguenza diventa più difficile accontentarsi di una soluzione.
Concentrati sulle tue esigenze specifiche, che sembrano avere a che fare con la creazione di un meccanismo di caricamento delle risorse che astragga la memoria di origine sottostante e permetta l'estensibilità del set di tipi supportati. Non c'è davvero nulla nella tua domanda riguardante, ad esempio, la memorizzazione nella cache delle risorse già caricate - il che va bene, perché in conformità con il principio della responsabilità singola dovresti probabilmente costruire una cache delle risorse come entità separata e aggregare le due interfacce altrove , a seconda dei casi.
Per rispondere alla tua specifica preoccupazione, dovresti progettare il tuo caricatore in modo che non esegua il caricamento di alcun asset stesso, ma piuttosto deleghi tale responsabilità alle interfacce su misura per il caricamento di specifici tipi di asset. Per esempio:
interface ITypeLoader {
object Load (Stream assetStream);
}
È possibile creare nuove classi che implementano questa interfaccia, con ogni nuova classe adattata al caricamento di un tipo specifico di dati da un flusso. Usando un flusso, il caricatore di tipi può essere scritto su un'interfaccia comune, indipendente dallo spazio di archiviazione, e non deve essere codificato per caricare dal disco o da un database; ciò ti consentirebbe persino di caricare le tue risorse dagli stream di rete (che può essere molto utile nell'implementazione del hot-ricaricamento delle risorse quando il gioco è in esecuzione su una console e gli strumenti di modifica su un PC collegato in rete).
Il tuo caricatore di risorse principale deve essere in grado di registrare e tracciare questi caricatori specifici per tipo:
class AssetLoader {
public void RegisterType (string key, ITypeLoader loader) {
loaders[key] = loader;
}
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
La "chiave" usata qui può essere quella che ti piace - e non è necessario che sia una stringa, ma sono facili da iniziare. La chiave determinerà il modo in cui ti aspetti che un utente identifichi una determinata risorsa e verrà utilizzato per cercare il caricatore appropriato. Poiché si desidera nascondere il fatto che l'implementazione potrebbe utilizzare un file system o un database, non è possibile avere utenti che fanno riferimento a risorse tramite un percorso del file system o qualcosa del genere.
Gli utenti devono fare riferimento a una risorsa con un minimo di informazioni. In alcuni casi, sarebbe sufficiente un solo nome di file, ma ho scoperto che spesso è preferibile utilizzare una coppia tipo / nome, quindi tutto è molto esplicito. Pertanto, un utente potrebbe fare riferimento a un'istanza denominata di uno dei file XML di animazione come "AnimationXml","PlayerWalkCycle"
.
Qui, AnimationXml
sarebbe la chiave con cui ti sei registrato AnimationXmlLoader
, che implementa IAssetLoader
. Ovviamente, PlayerWalkCycle
identifica l'asset specifico. Dato un nome di tipo e un nome di risorsa, il caricatore di risorse può eseguire una query sulla memoria permanente per i byte non elaborati di tale risorsa. Dal momento che stiamo cercando la massima generalità qui, puoi implementarlo passando al caricatore un mezzo di accesso allo storage quando lo crei, permettendoti di sostituire il supporto di archiviazione con qualsiasi cosa che possa fornire un flusso in seguito:
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 provider di stream molto semplice dovrebbe semplicemente cercare in una directory radice dell'asset specificata una sottodirectory denominata type
e caricare i byte grezzi del file denominato name
in uno stream e restituirlo.
In breve, quello che hai qui è un sistema in cui:
- Esiste una classe che sa leggere byte grezzi da una sorta di memoria back-end (un disco, un database, un flusso di rete, qualunque cosa).
- Esistono classi che sanno come trasformare un flusso di byte non elaborato in un tipo specifico di risorsa e restituirlo.
- Il tuo "caricatore di risorse" attuale ha solo una raccolta delle suddette dimensioni e sa come convogliare l'output del provider di flussi nel caricatore specifico del tipo e quindi produrre un bene concreto. Esponendo i modi per configurare il provider di flusso e i caricatori specifici del tipo, si dispone di un sistema che può essere esteso dai client (o dall'utente) senza dover modificare il codice del caricatore di risorse effettivo.
Alcuni avvertimenti e note finali:
Il codice sopra è sostanzialmente C #, ma dovrebbe tradursi in quasi tutte le lingue con il minimo sforzo. Per facilitare ciò ho omesso molte cose come il controllo degli errori o l'uso corretto IDisposable
e altri modi di dire che potrebbero non essere applicabili direttamente in altre lingue. Questi sono lasciati come compiti per il lettore.
Allo stesso modo, restituisco la risorsa concreta come object
sopra, ma puoi usare generici o modelli o qualsiasi altra cosa per produrre un tipo di oggetto più specifico se vuoi (dovresti, è bello lavorare con).
Come sopra, non mi occupo affatto di cache qui. Tuttavia, è possibile aggiungere la memorizzazione nella cache facilmente e con lo stesso tipo di generalità e configurabilità. Provalo e vedi!
Ci sono molti modi per farlo, e certamente non esiste un modo o un consenso, motivo per cui non sei stato in grado di trovarne uno. Ho provato a fornire abbastanza codice per ottenere i punti specifici senza trasformare questa risposta in un wall-of-code penosamente lungo. È già estremamente lungo così com'è. Se hai domande chiare, sentiti libero di commentare o di trovarmi nella chat .