Show More
Commit Description:
Add timers for Simulation and various engines...
Commit Description:
Add timers for Simulation and various engines
Starting to add additional timers for different stages of the process of
updating in order to get more insight into what is slowing it down.
The update takes 9ms, which is much longer than it used to.
Engine-specific timers are coming later.
References:
File last commit:
Show/Diff file:
Action:
FNA/src/Content/ContentManager.cs
639 lines | 16.2 KiB | text/x-csharp | CSharpLexer
639 lines | 16.2 KiB | text/x-csharp | CSharpLexer
r0 | #region License | |||
/* FNA - XNA4 Reimplementation for Desktop Platforms | ||||
* Copyright 2009-2020 Ethan Lee and the MonoGame Team | ||||
* | ||||
* Released under the Microsoft Public License. | ||||
* See LICENSE for details. | ||||
*/ | ||||
#endregion | ||||
#region Using Statements | ||||
using System; | ||||
using System.Collections.Generic; | ||||
using System.Diagnostics; | ||||
using System.IO; | ||||
using System.Reflection; | ||||
using Microsoft.Xna.Framework.Audio; | ||||
using Microsoft.Xna.Framework.Graphics; | ||||
using Microsoft.Xna.Framework.Media; | ||||
#endregion | ||||
namespace Microsoft.Xna.Framework.Content | ||||
{ | ||||
public partial class ContentManager : IDisposable | ||||
{ | ||||
#region Public ServiceProvider Property | ||||
public IServiceProvider ServiceProvider | ||||
{ | ||||
get; | ||||
private set; | ||||
} | ||||
#endregion | ||||
#region Public RootDirectory Property | ||||
public string RootDirectory | ||||
{ | ||||
get; | ||||
set; | ||||
} | ||||
#endregion | ||||
#region Internal Root Directory Path Property | ||||
internal string RootDirectoryFullPath | ||||
{ | ||||
get | ||||
{ | ||||
if (Path.IsPathRooted(RootDirectory)) | ||||
{ | ||||
return RootDirectory; | ||||
} | ||||
return Path.Combine(TitleLocation.Path, RootDirectory); | ||||
} | ||||
} | ||||
#endregion | ||||
#region Private Variables | ||||
private GraphicsDevice graphicsDevice; | ||||
private Dictionary<string, object> loadedAssets = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); | ||||
private List<IDisposable> disposableAssets = new List<IDisposable>(); | ||||
private bool disposed; | ||||
#endregion | ||||
#region Private Static Variables | ||||
private static object ContentManagerLock = new object(); | ||||
private static List<WeakReference> ContentManagers = new List<WeakReference>(); | ||||
private static readonly byte[] xnbHeader = new byte[4]; | ||||
private static List<char> targetPlatformIdentifiers = new List<char>() | ||||
{ | ||||
'w', // Windows (DirectX) | ||||
'x', // Xbox360 | ||||
'm', // WindowsPhone | ||||
'i', // iOS | ||||
'a', // Android | ||||
'd', // DesktopGL | ||||
'X', // MacOSX | ||||
'W', // WindowsStoreApp | ||||
'n', // NativeClient | ||||
'u', // Ouya | ||||
'p', // PlayStationMobile | ||||
'M', // WindowsPhone8 | ||||
'r', // RaspberryPi | ||||
'P', // Playstation 4 | ||||
'g', // WindowsGL (deprecated for DesktopGL) | ||||
'l', // Linux (deprecated for DesktopGL) | ||||
}; | ||||
#endregion | ||||
#region Public Constructors | ||||
public ContentManager(IServiceProvider serviceProvider) | ||||
{ | ||||
if (serviceProvider == null) | ||||
{ | ||||
throw new ArgumentNullException("serviceProvider"); | ||||
} | ||||
ServiceProvider = serviceProvider; | ||||
RootDirectory = string.Empty; | ||||
AddContentManager(this); | ||||
} | ||||
public ContentManager(IServiceProvider serviceProvider, string rootDirectory) | ||||
{ | ||||
if (serviceProvider == null) | ||||
{ | ||||
throw new ArgumentNullException("serviceProvider"); | ||||
} | ||||
if (rootDirectory == null) | ||||
{ | ||||
throw new ArgumentNullException("rootDirectory"); | ||||
} | ||||
ServiceProvider = serviceProvider; | ||||
RootDirectory = rootDirectory; | ||||
AddContentManager(this); | ||||
} | ||||
#endregion | ||||
#region Destructor | ||||
/* Use C# destructor syntax for finalization code. | ||||
* This destructor will run only if the Dispose method | ||||
* does not get called. | ||||
* It gives your base class the opportunity to finalize. | ||||
* Do not provide destructors in types derived from this class. | ||||
*/ | ||||
~ContentManager() | ||||
{ | ||||
/* Do not re-create Dispose clean-up code here. | ||||
* Calling Dispose(false) is optimal in terms of | ||||
* readability and maintainability. | ||||
*/ | ||||
Dispose(false); | ||||
} | ||||
#endregion | ||||
#region Dispose Methods | ||||
public void Dispose() | ||||
{ | ||||
Dispose(true); | ||||
/* Tell the garbage collector not to call the finalizer | ||||
* since all the cleanup will already be done. | ||||
*/ | ||||
GC.SuppressFinalize(this); | ||||
// Once disposed, content manager wont be used again | ||||
RemoveContentManager(this); | ||||
} | ||||
/* If disposing is true, it was called explicitly and we should dispose managed | ||||
* objects. If disposing is false, it was called by the finalizer and managed | ||||
* objects should not be disposed. | ||||
*/ | ||||
protected virtual void Dispose(bool disposing) | ||||
{ | ||||
if (!disposed) | ||||
{ | ||||
if (disposing) | ||||
{ | ||||
Unload(); | ||||
} | ||||
disposed = true; | ||||
} | ||||
} | ||||
#endregion | ||||
#region Public Methods | ||||
public virtual T Load<T>(string assetName) | ||||
{ | ||||
if (string.IsNullOrEmpty(assetName)) | ||||
{ | ||||
throw new ArgumentNullException("assetName"); | ||||
} | ||||
if (disposed) | ||||
{ | ||||
throw new ObjectDisposedException("ContentManager"); | ||||
} | ||||
T result = default(T); | ||||
/* On some platforms, name and slash direction matter. | ||||
* We store the asset by a /-separating key rather than | ||||
* how the path to the file was passed to us to avoid | ||||
* loading "content/asset1.xnb" and "content\\ASSET1.xnb" | ||||
* as if they were two different files. this matches | ||||
* stock XNA behavior. The Dictionary will ignore case | ||||
* differences. | ||||
*/ | ||||
string key = assetName.Replace('\\', '/'); | ||||
// Check for a previously loaded asset first | ||||
object asset = null; | ||||
if (loadedAssets.TryGetValue(key, out asset)) | ||||
{ | ||||
if (asset is T) | ||||
{ | ||||
return (T) asset; | ||||
} | ||||
} | ||||
// Load the asset. | ||||
result = ReadAsset<T>(assetName, null); | ||||
loadedAssets[key] = result; | ||||
return result; | ||||
} | ||||
public virtual void Unload() | ||||
{ | ||||
// Look for disposable assets. | ||||
foreach (IDisposable disposable in disposableAssets) | ||||
{ | ||||
if (disposable != null) | ||||
{ | ||||
disposable.Dispose(); | ||||
} | ||||
} | ||||
disposableAssets.Clear(); | ||||
loadedAssets.Clear(); | ||||
} | ||||
#endregion | ||||
#region Protected Methods | ||||
protected virtual Stream OpenStream(string assetName) | ||||
{ | ||||
Stream stream; | ||||
try | ||||
{ | ||||
stream = TitleContainer.OpenStream( | ||||
Path.Combine(RootDirectory, assetName) + ".xnb" | ||||
); | ||||
} | ||||
catch (FileNotFoundException fileNotFound) | ||||
{ | ||||
throw new ContentLoadException("The content file was not found.", fileNotFound); | ||||
} | ||||
catch (DirectoryNotFoundException directoryNotFound) | ||||
{ | ||||
throw new ContentLoadException("The directory was not found.", directoryNotFound); | ||||
} | ||||
catch (Exception exception) | ||||
{ | ||||
throw new ContentLoadException("Opening stream error.", exception); | ||||
} | ||||
return stream; | ||||
} | ||||
protected T ReadAsset<T>(string assetName, Action<IDisposable> recordDisposableObject) | ||||
{ | ||||
if (string.IsNullOrEmpty(assetName)) | ||||
{ | ||||
throw new ArgumentNullException("assetName"); | ||||
} | ||||
if (disposed) | ||||
{ | ||||
throw new ObjectDisposedException("ContentManager"); | ||||
} | ||||
object result = null; | ||||
Stream stream = null; | ||||
string modifiedAssetName = String.Empty; // Will be used if we have to guess a filename | ||||
try | ||||
{ | ||||
stream = OpenStream(assetName); | ||||
} | ||||
catch (Exception e) | ||||
{ | ||||
// Okay, so we couldn't open it. Maybe it needs a different extension? | ||||
// FIXME: This only works for files on the disk, what about custom streams? -flibit | ||||
modifiedAssetName = MonoGame.Utilities.FileHelpers.NormalizeFilePathSeparators( | ||||
Path.Combine(RootDirectoryFullPath, assetName) | ||||
); | ||||
if (typeof(T) == typeof(Texture2D) || typeof(T) == typeof(Texture)) | ||||
{ | ||||
modifiedAssetName = Texture2DReader.Normalize(modifiedAssetName); | ||||
} | ||||
else if ((typeof(T) == typeof(SoundEffect))) | ||||
{ | ||||
modifiedAssetName = SoundEffectReader.Normalize(modifiedAssetName); | ||||
} | ||||
else if ((typeof(T) == typeof(Effect))) | ||||
{ | ||||
modifiedAssetName = EffectReader.Normalize(modifiedAssetName); | ||||
} | ||||
else if ((typeof(T) == typeof(Song))) | ||||
{ | ||||
modifiedAssetName = SongReader.Normalize(modifiedAssetName); | ||||
} | ||||
else if ((typeof(T) == typeof(Video))) | ||||
{ | ||||
modifiedAssetName = VideoReader.Normalize(modifiedAssetName); | ||||
} | ||||
else | ||||
{ | ||||
// No raw format available, disregard! | ||||
modifiedAssetName = null; | ||||
} | ||||
// Did we get anything...? | ||||
if (String.IsNullOrEmpty(modifiedAssetName)) | ||||
{ | ||||
// Nope, nothing we're aware of! | ||||
throw new ContentLoadException( | ||||
"Could not load asset " + assetName + "! Error: " + e.Message, | ||||
e | ||||
); | ||||
} | ||||
stream = TitleContainer.OpenStream(modifiedAssetName); | ||||
} | ||||
// Check for XNB header | ||||
stream.Read(xnbHeader, 0, xnbHeader.Length); | ||||
if ( xnbHeader[0] == 'X' && | ||||
xnbHeader[1] == 'N' && | ||||
xnbHeader[2] == 'B' && | ||||
targetPlatformIdentifiers.Contains((char) xnbHeader[3]) ) | ||||
{ | ||||
using (BinaryReader xnbReader = new BinaryReader(stream)) | ||||
using (ContentReader reader = GetContentReaderFromXnb(assetName, ref stream, xnbReader, (char) xnbHeader[3], recordDisposableObject)) | ||||
{ | ||||
result = reader.ReadAsset<T>(); | ||||
GraphicsResource resource = result as GraphicsResource; | ||||
if (resource != null) | ||||
{ | ||||
resource.Name = assetName; | ||||
} | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
// It's not an XNB file. Try to load as a raw asset instead. | ||||
// FIXME: Assuming seekable streams! -flibit | ||||
stream.Seek(0, SeekOrigin.Begin); | ||||
if (typeof(T) == typeof(Texture2D) || typeof(T) == typeof(Texture)) | ||||
{ | ||||
Texture2D texture; | ||||
if ( xnbHeader[0] == 'D' && | ||||
xnbHeader[1] == 'D' && | ||||
xnbHeader[2] == 'S' && | ||||
xnbHeader[3] == ' ' ) | ||||
{ | ||||
texture = Texture2D.DDSFromStreamEXT( | ||||
GetGraphicsDevice(), | ||||
stream | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
texture = Texture2D.FromStream( | ||||
GetGraphicsDevice(), | ||||
stream | ||||
); | ||||
} | ||||
texture.Name = assetName; | ||||
result = texture; | ||||
} | ||||
else if ((typeof(T) == typeof(SoundEffect))) | ||||
{ | ||||
result = SoundEffect.FromStream(stream); | ||||
} | ||||
else if ((typeof(T) == typeof(Effect))) | ||||
{ | ||||
byte[] data = new byte[stream.Length]; | ||||
stream.Read(data, 0, (int) stream.Length); | ||||
result = new Effect(GetGraphicsDevice(), data); | ||||
} | ||||
else if ((typeof(T) == typeof(Song))) | ||||
{ | ||||
// FIXME: Not using the stream! -flibit | ||||
result = new Song(modifiedAssetName); | ||||
} | ||||
else if ((typeof(T) == typeof(Video))) | ||||
{ | ||||
// FIXME: Not using the stream! -flibit | ||||
result = new Video(modifiedAssetName, GetGraphicsDevice()); | ||||
FNALoggerEXT.LogWarn( | ||||
"Video " + | ||||
modifiedAssetName + | ||||
" does not have an XNB file! Hacking Duration property!" | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
stream.Close(); | ||||
throw new ContentLoadException("Could not load " + assetName + " asset!"); | ||||
} | ||||
/* Because Raw Assets skip the ContentReader step, they need to have their | ||||
* disposables recorded here. Doing it outside of this catch will | ||||
* result in disposables being logged twice. | ||||
*/ | ||||
IDisposable disposableResult = result as IDisposable; | ||||
if (disposableResult != null) | ||||
{ | ||||
if (recordDisposableObject != null) | ||||
{ | ||||
recordDisposableObject(disposableResult); | ||||
} | ||||
else | ||||
{ | ||||
disposableAssets.Add(disposableResult); | ||||
} | ||||
} | ||||
/* Because we're not using a BinaryReader for raw assets, we | ||||
* need to close the stream ourselves. | ||||
* -flibit | ||||
*/ | ||||
stream.Close(); | ||||
} | ||||
return (T) result; | ||||
} | ||||
#endregion | ||||
#region Internal Methods | ||||
internal void RecordDisposable(IDisposable disposable) | ||||
{ | ||||
Debug.Assert(disposable != null, "The disposable is null!"); | ||||
/* Avoid recording disposable objects twice. ReloadAsset will try to record | ||||
* the disposables again. We don't know which asset recorded which | ||||
* disposable so just guard against storing multiple of the same instance. | ||||
*/ | ||||
if (!disposableAssets.Contains(disposable)) | ||||
{ | ||||
disposableAssets.Add(disposable); | ||||
} | ||||
} | ||||
internal GraphicsDevice GetGraphicsDevice() | ||||
{ | ||||
if (graphicsDevice == null) | ||||
{ | ||||
IGraphicsDeviceService result = ServiceProvider.GetService( | ||||
typeof(IGraphicsDeviceService) | ||||
) as IGraphicsDeviceService; | ||||
if (result == null) | ||||
{ | ||||
throw new ContentLoadException("No Graphics Device Service"); | ||||
} | ||||
graphicsDevice = result.GraphicsDevice; | ||||
} | ||||
return graphicsDevice; | ||||
} | ||||
#endregion | ||||
#region Private Methods | ||||
private ContentReader GetContentReaderFromXnb(string originalAssetName, ref Stream stream, BinaryReader xnbReader, char platform, Action<IDisposable> recordDisposableObject) | ||||
{ | ||||
byte version = xnbReader.ReadByte(); | ||||
byte flags = xnbReader.ReadByte(); | ||||
bool compressed = (flags & 0x80) != 0; | ||||
if (version != 5 && version != 4) | ||||
{ | ||||
throw new ContentLoadException("Invalid XNB version"); | ||||
} | ||||
// The next int32 is the length of the XNB file | ||||
int xnbLength = xnbReader.ReadInt32(); | ||||
ContentReader reader; | ||||
if (compressed) | ||||
{ | ||||
/* Decompress the XNB | ||||
* Thanks to ShinAli (https://bitbucket.org/alisci01/xnbdecompressor) | ||||
*/ | ||||
int compressedSize = xnbLength - 14; | ||||
int decompressedSize = xnbReader.ReadInt32(); | ||||
// This will replace the XNB stream at the end | ||||
MemoryStream decompressedStream = new MemoryStream( | ||||
new byte[decompressedSize], | ||||
0, | ||||
decompressedSize, | ||||
true, | ||||
true // This MUST be true! Readers may need GetBuffer()! | ||||
); | ||||
/* Read in the whole XNB file at once, into a temp buffer. | ||||
* For slow disks, the extra malloc is more than worth the | ||||
* performance improvement from not constantly fread()ing! | ||||
*/ | ||||
MemoryStream compressedStream = new MemoryStream( | ||||
new byte[compressedSize], | ||||
0, | ||||
compressedSize, | ||||
true, | ||||
true | ||||
); | ||||
stream.Read(compressedStream.GetBuffer(), 0, compressedSize); | ||||
// Default window size for XNB encoded files is 64Kb (need 16 bits to represent it) | ||||
LzxDecoder dec = new LzxDecoder(16); | ||||
int decodedBytes = 0; | ||||
long pos = 0; | ||||
while (pos < compressedSize) | ||||
{ | ||||
/* The compressed stream is separated into blocks that will | ||||
* decompress into 32kB or some other size if specified. | ||||
* Normal, 32kB output blocks will have a short indicating | ||||
* the size of the block before the block starts. Blocks | ||||
* that have a defined output will be preceded by a byte of | ||||
* value 0xFF (255), then a short indicating the output size | ||||
* and another for the block size. All shorts for these | ||||
* cases are encoded in big endian order. | ||||
*/ | ||||
int hi = compressedStream.ReadByte(); | ||||
int lo = compressedStream.ReadByte(); | ||||
int block_size = (hi << 8) | lo; | ||||
int frame_size = 0x8000; // Frame size is 32kB by default | ||||
// Does this block define a frame size? | ||||
if (hi == 0xFF) | ||||
{ | ||||
hi = lo; | ||||
lo = (byte) compressedStream.ReadByte(); | ||||
frame_size = (hi << 8) | lo; | ||||
hi = (byte) compressedStream.ReadByte(); | ||||
lo = (byte) compressedStream.ReadByte(); | ||||
block_size = (hi << 8) | lo; | ||||
pos += 5; | ||||
} | ||||
else | ||||
{ | ||||
pos += 2; | ||||
} | ||||
// Either says there is nothing to decode | ||||
if (block_size == 0 || frame_size == 0) | ||||
{ | ||||
break; | ||||
} | ||||
dec.Decompress(compressedStream, block_size, decompressedStream, frame_size); | ||||
pos += block_size; | ||||
decodedBytes += frame_size; | ||||
/* Reset the position of the input just in case the bit | ||||
* buffer read in some unused bytes. | ||||
*/ | ||||
compressedStream.Seek(pos, SeekOrigin.Begin); | ||||
} | ||||
if (decompressedStream.Position != decompressedSize) | ||||
{ | ||||
throw new ContentLoadException( | ||||
"Decompression of " + originalAssetName + " failed. " | ||||
); | ||||
} | ||||
decompressedStream.Seek(0, SeekOrigin.Begin); | ||||
reader = new ContentReader( | ||||
this, | ||||
decompressedStream, | ||||
originalAssetName, | ||||
version, | ||||
platform, | ||||
recordDisposableObject | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
reader = new ContentReader( | ||||
this, | ||||
stream, | ||||
originalAssetName, | ||||
version, | ||||
platform, | ||||
recordDisposableObject | ||||
); | ||||
} | ||||
return reader; | ||||
} | ||||
#endregion | ||||
#region Private Static Methods | ||||
private static void AddContentManager(ContentManager contentManager) | ||||
{ | ||||
lock (ContentManagerLock) | ||||
{ | ||||
/* Check if the list contains this content manager already. Also take | ||||
* the opportunity to prune the list of any finalized content managers. | ||||
*/ | ||||
bool contains = false; | ||||
for (int i = ContentManagers.Count - 1; i >= 0; i -= 1) | ||||
{ | ||||
WeakReference contentRef = ContentManagers[i]; | ||||
if (ReferenceEquals(contentRef.Target, contentManager)) | ||||
{ | ||||
contains = true; | ||||
} | ||||
if (!contentRef.IsAlive) | ||||
{ | ||||
ContentManagers.RemoveAt(i); | ||||
} | ||||
} | ||||
if (!contains) | ||||
{ | ||||
ContentManagers.Add(new WeakReference(contentManager)); | ||||
} | ||||
} | ||||
} | ||||
private static void RemoveContentManager(ContentManager contentManager) | ||||
{ | ||||
lock (ContentManagerLock) | ||||
{ | ||||
/* Check if the list contains this content manager and remove it. Also | ||||
* take the opportunity to prune the list of any finalized content managers. | ||||
*/ | ||||
for (int i = ContentManagers.Count - 1; i >= 0; i -= 1) | ||||
{ | ||||
WeakReference contentRef = ContentManagers[i]; | ||||
if (!contentRef.IsAlive || ReferenceEquals(contentRef.Target, contentManager)) | ||||
{ | ||||
ContentManagers.RemoveAt(i); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
#endregion | ||||
} | ||||
} | ||||