forked from MonoGame/MonoGame
-
Notifications
You must be signed in to change notification settings - Fork 5
/
ContentStatsCollection.cs
229 lines (199 loc) · 8.77 KB
/
ContentStatsCollection.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// MonoGame - Copyright (C) MonoGame Foundation, Inc
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.Xna.Framework.Content.Pipeline
{
/// <summary>
/// A collection of content building statistics for use in diagnosing content issues.
/// </summary>
public class ContentStatsCollection
{
private static readonly string _header = "Source File,Dest File,Processor Type,Content Type,Source File Size,Dest File Size,Build Seconds";
private static readonly Regex _split = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
private readonly object _locker = new object();
private readonly Dictionary<string, ContentStats> _statsBySource = new Dictionary<string, ContentStats>(1024);
public static readonly string Extension = ".mgstats";
/// <summary>
/// Optionally used for copying stats that were stored in another collection.
/// </summary>
public ContentStatsCollection PreviousStats { get; set; }
/// <summary>
/// The internal content statistics dictionary.
/// </summary>
public IReadOnlyDictionary<string, ContentStats> Stats
{
get { return _statsBySource; }
}
/// <summary>
/// Get the content statistics for a source file and returns true if found.
/// </summary>
public bool TryGetStats(string sourceFile, out ContentStats stats)
{
lock (_locker)
{
if (!_statsBySource.TryGetValue(sourceFile, out stats))
return false;
return true;
}
}
/// <summary>
/// Clears all the content statistics.
/// </summary>
public void Reset()
{
lock (_locker)
_statsBySource.Clear();
}
/// <summary>
/// Store content building stats for a source file.
/// </summary>
/// <param name="sourceFile">The absolute path to the source asset file.</param>
/// <param name="destFile">The absolute path to the destination content file.</param>
/// <param name="processorType">The type name of the content processor.</param>
/// <param name="contentType">The content type object.</param>
/// <param name="buildSeconds">The build time in seconds.</param>
public void RecordStats(string sourceFile, string destFile, string processorType, Type contentType, float buildSeconds)
{
var sourceSize = new FileInfo(sourceFile).Length;
var destSize = new FileInfo(destFile).Length;
lock (_locker)
{
ContentStats stats;
_statsBySource.TryGetValue(sourceFile, out stats);
stats.SourceFile = sourceFile;
stats.DestFile = destFile;
stats.SourceFileSize = sourceSize;
stats.DestFileSize = destSize;
stats.ContentType = GetFriendlyTypeName(contentType);
stats.ProcessorType = processorType;
stats.BuildSeconds = buildSeconds;
_statsBySource[stats.SourceFile] = stats;
}
}
/// <summary>
/// Copy content building stats to the current collection from the PreviousStats.
/// </summary>
/// <param name="sourceFile">The absolute path to the source asset file.</param>
public void CopyPreviousStats(string sourceFile)
{
if (PreviousStats == null)
return;
lock (_locker)
{
if (_statsBySource.ContainsKey(sourceFile))
return;
ContentStats stats;
if (PreviousStats.TryGetStats(sourceFile, out stats))
_statsBySource[stats.SourceFile] = stats;
}
}
private static string GetFriendlyTypeName(Type type)
{
if (type == null)
return "";
if (type == typeof(int))
return "int";
else if (type == typeof(short))
return "short";
else if (type == typeof(byte))
return "byte";
else if (type == typeof(bool))
return "bool";
else if (type == typeof(long))
return "long";
else if (type == typeof(float))
return "float";
else if (type == typeof(double))
return "double";
else if (type == typeof(decimal))
return "decimal";
else if (type == typeof(string))
return "string";
else if (type.IsArray)
return GetFriendlyTypeName(type.GetElementType()) + "[" + new string(',', type.GetArrayRank() - 1) + "]";
else if (type.IsGenericType)
return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(x => GetFriendlyTypeName(x)).ToArray()) + ">";
else
return type.Name;
}
/// <summary>
/// Load the content statistics from a folder.
/// </summary>
/// <param name="outputPath">The folder where the .mgstats file can be found.</param>
/// <returns>Returns the content statistics or an empty collection.</returns>
public static ContentStatsCollection Read(string outputPath)
{
var collection = new ContentStatsCollection();
var filePath = Path.Combine(outputPath, Extension);
try
{
var lines = File.ReadAllLines(filePath);
// The first line is the CSV header... if it doesn't match then
// assume the data is invalid or changed formats.
if (lines[0] != _header)
return collection;
for (var i = 1; i < lines.Length; i++)
{
var columns = _split.Split(lines[i]);
if (columns.Length != 7)
continue;
ContentStats stats;
stats.SourceFile = columns[0].Trim('"');
stats.DestFile = columns[1].Trim('"');
stats.ProcessorType = columns[2].Trim('"');
stats.ContentType = columns[3].Trim('"');
stats.SourceFileSize = long.Parse(columns[4]);
stats.DestFileSize = long.Parse(columns[5]);
stats.BuildSeconds = float.Parse(columns[6]);
if (!collection._statsBySource.ContainsKey(stats.SourceFile))
collection._statsBySource.Add(stats.SourceFile, stats);
}
}
catch (Exception ex)
{
// Assume the file didn't exist or was incorrectly
// formatted... either way we start from fresh data.
collection.Reset();
}
return collection;
}
/// <summary>
/// Write the content statistics to a folder with the .mgstats file name.
/// </summary>
/// <param name="outputPath">The folder to write the .mgstats file.</param>
public void Write(string outputPath)
{
// ensure the output folder exists
Directory.CreateDirectory(outputPath);
var filePath = Path.Combine(outputPath, Extension);
using (var textWriter = new StreamWriter(filePath, false, new UTF8Encoding(false)))
{
// Sort the items alphabetically to ensure a consistent output
// and better mergability of the resulting file.
var contentStats = _statsBySource.Values.OrderBy(c => c.SourceFile, StringComparer.InvariantCulture).ToList();
textWriter.WriteLine(_header);
foreach (var stats in contentStats)
textWriter.WriteLine("\"{0}\",\"{1}\",\"{2}\",\"{3}\",{4},{5},{6}", stats.SourceFile, stats.DestFile, stats.ProcessorType, stats.ContentType, stats.SourceFileSize, stats.DestFileSize, stats.BuildSeconds);
}
}
/// <summary>
/// Merge in statistics from PreviousStats that do not exist in this collection.
/// </summary>
public void MergePreviousStats()
{
if (PreviousStats == null)
return;
foreach (var stats in PreviousStats._statsBySource.Values)
{
if (!_statsBySource.ContainsKey(stats.SourceFile))
_statsBySource.Add(stats.SourceFile, stats);
}
}
}
}