From 76d5a4288aaea4a3a611400d3fd0b193c7882cfa Mon Sep 17 00:00:00 2001 From: gered Date: Sat, 24 Aug 2013 11:18:39 -0400 Subject: [PATCH] add ported content/asset management code --- .../Blarg.GameFramework.csproj | 6 + .../Content/ContentContainer.cs | 19 ++ Blarg.GameFramework/Content/ContentManager.cs | 150 +++++++++++++ .../Content/DictionaryStoreContentLoader.cs | 205 ++++++++++++++++++ Blarg.GameFramework/Content/IContentLoader.cs | 12 + .../Content/IContentLoaderBase.cs | 16 ++ 6 files changed, 408 insertions(+) create mode 100644 Blarg.GameFramework/Content/ContentContainer.cs create mode 100644 Blarg.GameFramework/Content/ContentManager.cs create mode 100644 Blarg.GameFramework/Content/DictionaryStoreContentLoader.cs create mode 100644 Blarg.GameFramework/Content/IContentLoader.cs create mode 100644 Blarg.GameFramework/Content/IContentLoaderBase.cs diff --git a/Blarg.GameFramework/Blarg.GameFramework.csproj b/Blarg.GameFramework/Blarg.GameFramework.csproj index 499003c..184137c 100644 --- a/Blarg.GameFramework/Blarg.GameFramework.csproj +++ b/Blarg.GameFramework/Blarg.GameFramework.csproj @@ -169,6 +169,11 @@ + + + + + @@ -189,6 +194,7 @@ + diff --git a/Blarg.GameFramework/Content/ContentContainer.cs b/Blarg.GameFramework/Content/ContentContainer.cs new file mode 100644 index 0000000..5be3909 --- /dev/null +++ b/Blarg.GameFramework/Content/ContentContainer.cs @@ -0,0 +1,19 @@ +using System; + +namespace Blarg.GameFramework.Content +{ + public class ContentContainer where T : class + { + public T Content; + public bool IsPinned; + + internal ContentContainer(T content) + { + if (content == null) + throw new ArgumentNullException("content"); + + Content = content; + IsPinned = false; + } + } +} diff --git a/Blarg.GameFramework/Content/ContentManager.cs b/Blarg.GameFramework/Content/ContentManager.cs new file mode 100644 index 0000000..b0ea68b --- /dev/null +++ b/Blarg.GameFramework/Content/ContentManager.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; + +namespace Blarg.GameFramework.Content +{ + public class ContentManager : IDisposable + { + const string LOG_TAG = "CONTENT"; + + Dictionary _loaders; + + public bool IsLoaded { get; private set; } + public readonly IGameApp GameApp; + + public ContentManager(IGameApp gameApp) + { + if (gameApp == null) + throw new ArgumentNullException("gameApp"); + + _loaders = new Dictionary(); + IsLoaded = false; + GameApp = gameApp; + } + + public void RegisterLoader(IContentLoaderBase loader) + { + if (loader == null) + throw new ArgumentNullException("loader"); + if (_loaders.ContainsKey(loader.ContentType)) + throw new InvalidOperationException("Content loader already registered for this content type."); + + Framework.Logger.Info(LOG_TAG, "Registering content loader for type: {0}", loader.ContentType.ToString()); + _loaders.Add(loader.ContentType, loader); + } + + public T Get(string name) where T : class + { + var loader = GetLoader(); + if (loader == null) + throw new InvalidOperationException("No registered loader for this content type."); + else + return loader.Get(name, null); + + } + + public T Get(string name, object contentParameters) where T : class + { + var loader = GetLoader(); + if (loader == null) + throw new InvalidOperationException("No registered loader for this content type."); + else + return loader.Get(name, contentParameters); + } + + public T Pin(string name) where T : class + { + var loader = GetLoader(); + if (loader == null) + throw new InvalidOperationException("No registered loader for this content type."); + else + return loader.Pin(name, null); + } + + public T Pin(string name, object contentParameters) where T : class + { + var loader = GetLoader(); + if (loader == null) + throw new InvalidOperationException("No registered loader for this content type."); + else + return loader.Pin(name, contentParameters); + } + + public void RemoveAllContent(bool removePinnedContent = false) + { + foreach (var loader in _loaders) + loader.Value.RemoveAll(removePinnedContent); + } + + public void RemoveAllContent(bool removePinnedContent = false) where T : class + { + var loader = GetLoader(); + if (loader == null) + throw new InvalidOperationException("No registered loader for this content type."); + else + loader.RemoveAll(removePinnedContent); + } + + public string GetNameOf(T content) where T : class + { + if (content == null) + throw new ArgumentNullException("content"); + + var loader = GetLoader(); + if (loader == null) + throw new InvalidOperationException("No registered loader for this content type."); + else + return loader.GetNameOf(content); + } + + public IContentLoader GetLoader() where T : class + { + IContentLoaderBase loader; + _loaders.TryGetValue(typeof(T), out loader); + if (loader == null) + return null; + else + return loader as IContentLoader; + } + + public void OnLoad() + { + IsLoaded = true; + + Framework.Logger.Info(LOG_TAG, "Running all content loader OnLoad events."); + foreach (var loader in _loaders) + loader.Value.OnLoad(); + } + + public void OnUnload() + { + IsLoaded = false; + + Framework.Logger.Info(LOG_TAG, "Running all content loader OnUnload events."); + foreach (var loader in _loaders) + loader.Value.OnUnload(); + } + + public void OnLostContext() + { + Framework.Logger.Info(LOG_TAG, "Running all content loader OnLostContext events."); + foreach (var loader in _loaders) + loader.Value.OnLostContext(); + } + + public void OnNewContext() + { + Framework.Logger.Info(LOG_TAG, "Running all content loader OnNewContext events."); + foreach (var loader in _loaders) + loader.Value.OnNewContext(); + } + + public void Dispose() + { + Framework.Logger.Info(LOG_TAG, "Disposing"); + foreach (var loader in _loaders) + loader.Value.Dispose(); + _loaders.Clear(); + } + } +} diff --git a/Blarg.GameFramework/Content/DictionaryStoreContentLoader.cs b/Blarg.GameFramework/Content/DictionaryStoreContentLoader.cs new file mode 100644 index 0000000..de72320 --- /dev/null +++ b/Blarg.GameFramework/Content/DictionaryStoreContentLoader.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; + +namespace Blarg.GameFramework.Content +{ + public abstract class DictionaryStoreContentLoader : IContentLoader where T : class + { + const string LOG_TAG = "CONTENT"; + + string _defaultPath; + + protected readonly ContentManager ContentManager; + protected readonly Dictionary> ContentStore; + protected readonly string LoggingTag; + + public Type ContentType + { + get { return typeof(T); } + } + + public DictionaryStoreContentLoader(ContentManager contentManager, string loggingTag, string defaultPath) + { + if (contentManager == null) + throw new ArgumentNullException("contentManager"); + if (String.IsNullOrEmpty(loggingTag)) + throw new ArgumentException("Logging tag is required."); + + ContentManager = contentManager; + ContentStore = new Dictionary>(); + + LoggingTag = loggingTag; + + if (!String.IsNullOrEmpty(defaultPath)) + { + _defaultPath = defaultPath; + if (!_defaultPath.EndsWith("/")) + _defaultPath += "/"; + + Framework.Logger.Info(LOG_TAG, "{0}: default path set to \"{1}\"", LoggingTag, _defaultPath); + } + else + _defaultPath = ""; + } + + public T Get(string name, object contentParameters) + { + var content = GetContent(name, true, contentParameters); + if (content != null) + return content.Content; + else + return null; + } + + public T Pin(string name, object contentParameters) + { + var content = GetContent(name, true, contentParameters); + if (content != null) + { + content.IsPinned = true; + return content.Content; + } + else + return null; + } + + private ContentContainer GetContent(string name, bool loadIfNotAlready, object contentParameters) + { + if (!ContentManager.IsLoaded) + throw new InvalidOperationException("Cannot load content until OnLoad has passed."); + + ContentContainer content = null; + + string filename = AddDefaultPathIfNeeded(name); + filename = ProcessFilename(filename, contentParameters); + + var found = FindContent(filename, contentParameters); + if (found != null) + { + content = found.Value.Value; + } + else + { + if (loadIfNotAlready) + { + T actualContentObject = LoadContent(filename, contentParameters); + if (actualContentObject != null) + { + content = new ContentContainer(actualContentObject); + ContentStore.Add(filename, content); + } + } + } + + return content; + } + + public void RemoveAll(bool removePinnedContent = false) + { + if (ContentStore.Count == 0) + return; + + if (removePinnedContent) + { + Framework.Logger.Info(LOG_TAG, "{0}: freeing all content.", LoggingTag); + + // we're removing everything here. go through all the content and + // call Dispose() on all IDisposable's and then just clear the dictionary + foreach (var item in ContentStore) + { + var content = item.Value.Content; + if (content is IDisposable) + (content as IDisposable).Dispose(); + } + ContentStore.Clear(); + } + else + { + Framework.Logger.Info(LOG_TAG, "{0}: freeing all non-pinned content.", LoggingTag); + + // can't remove items from a dictionary inside a foreach loop iterating through it + // build a list of all the content (names) that need to be removed + var keysToRemove = new List(); + foreach (var item in ContentStore) + { + if (item.Value.IsPinned == false) + keysToRemove.Add(item.Key); + } + + // now remove each one by name + foreach (var key in keysToRemove) + { + var content = ContentStore[key].Content; + if (content is IDisposable) + (content as IDisposable).Dispose(); + ContentStore.Remove(key); + } + } + } + + public string GetNameOf(T content) + { + if (content == null) + throw new ArgumentNullException("content"); + + foreach (var item in ContentStore) + { + if (item.Value.Content == content) + return item.Key; + } + + return null; + } + + public virtual void OnLoad() + { + } + + public virtual void OnUnload() + { + } + + public virtual void OnNewContext() + { + } + + public virtual void OnLostContext() + { + } + + protected virtual KeyValuePair>? FindContent(string file, object contentParameters) + { + // default implementation here ignores any additional content parameters completely + + KeyValuePair>? result = null; + + ContentContainer content; + ContentStore.TryGetValue(file, out content); + if (content != null) + result = new KeyValuePair>(file, content); + + return result; + } + + protected abstract T LoadContent(string file, object contentParameters); + + protected string AddDefaultPathIfNeeded(string filename) + { + if (filename.StartsWith("/") || String.IsNullOrEmpty(_defaultPath) || filename.StartsWith("assets://")) + return filename; + else + return _defaultPath + filename; + } + + protected virtual string ProcessFilename(string filename, object contentParameters) + { + return filename; + } + + public void Dispose() + { + Framework.Logger.Info(LOG_TAG, "{0}: disposing. Forcibly freeing any content still loaded.", LoggingTag); + RemoveAll(true); + } + } +} diff --git a/Blarg.GameFramework/Content/IContentLoader.cs b/Blarg.GameFramework/Content/IContentLoader.cs new file mode 100644 index 0000000..2098add --- /dev/null +++ b/Blarg.GameFramework/Content/IContentLoader.cs @@ -0,0 +1,12 @@ +using System; + +namespace Blarg.GameFramework.Content +{ + public interface IContentLoader : IContentLoaderBase where T : class + { + T Get(string name, object contentParameters); + T Pin(string name, object contentParameters); + + string GetNameOf(T content); + } +} diff --git a/Blarg.GameFramework/Content/IContentLoaderBase.cs b/Blarg.GameFramework/Content/IContentLoaderBase.cs new file mode 100644 index 0000000..e9180d8 --- /dev/null +++ b/Blarg.GameFramework/Content/IContentLoaderBase.cs @@ -0,0 +1,16 @@ +using System; + +namespace Blarg.GameFramework.Content +{ + public interface IContentLoaderBase : IDisposable + { + Type ContentType { get; } + + void RemoveAll(bool removePinnedContent = false); + + void OnLoad(); + void OnUnload(); + void OnNewContext(); + void OnLostContext(); + } +}