418 lines
15 KiB
C#
418 lines
15 KiB
C#
using ANX.Framework.Content.Pipeline.Serialization.Compiler;
|
|
using ANX.Framework.Graphics;
|
|
using ANX.Framework.Content.Pipeline.Helpers;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Diagnostics;
|
|
|
|
namespace ANX.Framework.Content.Pipeline.Tasks
|
|
{
|
|
public class BuildContentTask
|
|
{
|
|
public delegate void PrepareAssetBuild(BuildContentTask sender, BuildItem item, out ContentImporterContext importerContext, out ContentProcessorContext processorContext);
|
|
|
|
private ImporterManager importerManager;
|
|
private ProcessorManager processorManager;
|
|
private ContentCompiler contentCompiler;
|
|
private MultiContentBuildLogger buildLogger = new MultiContentBuildLogger();
|
|
|
|
public BuildContentTask()
|
|
{
|
|
TargetPlatform = TargetPlatform.Windows;
|
|
CompressContent = false;
|
|
TargetProfile = GraphicsProfile.HiDef;
|
|
}
|
|
|
|
public PrepareAssetBuild PrepareAssetBuildCallback
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="ImporterManagerAdapter"/> that will be used to build the content.
|
|
/// </summary>
|
|
/// <remarks>The instance is automatically created when none is set.</remarks>
|
|
public ImporterManager ImporterManager
|
|
{
|
|
get
|
|
{
|
|
if (this.importerManager == null)
|
|
{
|
|
this.importerManager = new ImporterManager();
|
|
}
|
|
|
|
return this.importerManager;
|
|
}
|
|
set
|
|
{
|
|
this.importerManager = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="ProcessorManagerAdapter"/> that will be used to build the content.
|
|
/// </summary>
|
|
/// <remarks>The instance is automatically created when none is set.</remarks>
|
|
public ProcessorManager ProcessorManager
|
|
{
|
|
get
|
|
{
|
|
if (this.processorManager == null)
|
|
{
|
|
this.processorManager = new ProcessorManager();
|
|
}
|
|
|
|
return this.processorManager;
|
|
}
|
|
set
|
|
{
|
|
this.processorManager = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="ContentCompilerAdapter"/> that will be used to build the content.
|
|
/// </summary>
|
|
/// <remarks>The instance is automatically created when none is set.</remarks>
|
|
public ContentCompiler ContentCompiler
|
|
{
|
|
get
|
|
{
|
|
if (this.contentCompiler == null)
|
|
{
|
|
this.contentCompiler = new ContentCompiler();
|
|
}
|
|
|
|
return this.contentCompiler;
|
|
}
|
|
set
|
|
{
|
|
this.contentCompiler = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="IBuildCache"/> that will be used check if assets have to be rebuild.
|
|
/// </summary>
|
|
public IBuildCache BuildCache
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public TargetPlatform TargetPlatform
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public bool CompressContent
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public GraphicsProfile TargetProfile
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public Uri BaseDirectory
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public MultiContentBuildLogger BuildLogger
|
|
{
|
|
get { return buildLogger; }
|
|
}
|
|
|
|
public CompiledBuildItem Execute(BuildItem item)
|
|
{
|
|
if (item == null)
|
|
throw new ArgumentNullException("item");
|
|
|
|
return Execute(new [] { item }).First();
|
|
}
|
|
|
|
public CompiledBuildItem[] Execute(IEnumerable<BuildItem> itemsToBuild)
|
|
{
|
|
if (itemsToBuild == null)
|
|
throw new ArgumentNullException("itemsToBuild");
|
|
|
|
if (PrepareAssetBuildCallback == null)
|
|
throw new InvalidOperationException("The property PrepareAssetBuildCallback must be set to execute a build content task.");
|
|
|
|
List<CompiledBuildItem> result = new List<CompiledBuildItem>();
|
|
foreach (BuildItem buildItem in itemsToBuild)
|
|
{
|
|
if (buildItem == null)
|
|
throw new ArgumentNullException("An element of the parameter itemsToBuild is null.");
|
|
|
|
ContentImporterContext importerContext;
|
|
ContentProcessorContext processorContext;
|
|
PrepareAssetBuildCallback(this, buildItem, out importerContext, out processorContext);
|
|
|
|
if (string.IsNullOrEmpty(buildItem.ProcessorName))
|
|
throw new ArgumentNullException(string.Format("Asset \"{0}\" has no processor.", buildItem.AssetName));
|
|
|
|
//Has to be done before the cache is checked to make it has all the data it needs.
|
|
if (string.IsNullOrEmpty(buildItem.ImporterName))
|
|
buildItem.ImporterName = this.ImporterManager.GuessImporterByFileExtension(Path.GetExtension(buildItem.SourceFilename));
|
|
|
|
foreach (var parameter in this.ProcessorManager.GetProcessorParameters(buildItem.ProcessorName))
|
|
{
|
|
if (!buildItem.ProcessorParameters.ContainsKey(parameter.PropertyName))
|
|
buildItem.ProcessorParameters.Add(parameter.PropertyName, parameter.DefaultValue);
|
|
else
|
|
{
|
|
//Make sure the value is of the type it should be.
|
|
buildItem.ProcessorParameters[parameter.PropertyName] = TypeHelper.ConvertFromInvariantString(buildItem.ProcessorParameters[parameter.PropertyName], TypeHelper.GetType(parameter.PropertyType));
|
|
}
|
|
}
|
|
|
|
Uri outputFilename = new Uri(processorContext.OutputFilename, UriKind.Absolute);
|
|
if (BuildCache != null)
|
|
{
|
|
try
|
|
{
|
|
if (BuildCache.IsValid(buildItem, outputFilename))
|
|
{
|
|
result.Add(new CompiledBuildItem(null, buildItem, outputFilename.LocalPath, true));
|
|
continue;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
BuildLogger.LogWarning(null, new ContentIdentity() { SourceTool = "BuildCache" }, exc.Message);
|
|
Debugger.Break();
|
|
}
|
|
}
|
|
|
|
var absoluteFilename = MakeAbsolute(buildItem.SourceFilename);
|
|
|
|
object importedObject = null;
|
|
CompiledBuildItem compiled = null;
|
|
|
|
try
|
|
{
|
|
importedObject = ImportAsset(buildItem, absoluteFilename, importerContext);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
LogException(exc, absoluteFilename);
|
|
Debugger.Break();
|
|
}
|
|
|
|
if (importedObject != null)
|
|
{
|
|
if (String.IsNullOrEmpty(buildItem.ProcessorName))
|
|
{
|
|
buildItem.ProcessorName = ImporterManager.GetDefaultProcessor(buildItem.ImporterName);
|
|
if (string.IsNullOrEmpty(buildItem.ProcessorName))
|
|
{
|
|
buildItem.ProcessorName = ProcessorManager.GetProcessorForType(importedObject.GetType());
|
|
}
|
|
}
|
|
|
|
object compiledItem = null;
|
|
|
|
try
|
|
{
|
|
compiledItem = Process(buildItem, absoluteFilename, importedObject, processorContext);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
LogException(exc, absoluteFilename);
|
|
Debugger.Break();
|
|
}
|
|
|
|
if (compiledItem != null)
|
|
{
|
|
try
|
|
{
|
|
SerializeAsset(buildItem, compiledItem, processorContext.OutputDirectory, outputFilename.LocalPath);
|
|
|
|
compiled = new CompiledBuildItem(compiledItem, buildItem, outputFilename.LocalPath, true);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
LogException(exc, absoluteFilename);
|
|
Debugger.Break();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (compiled == null)
|
|
{
|
|
compiled = new CompiledBuildItem(null, buildItem, null, false);
|
|
}
|
|
|
|
result.Add(compiled);
|
|
|
|
if (compiled.Successfull)
|
|
{
|
|
BuildLogger.LogMessage("---\"{0}\" successfully build.", buildItem.AssetName);
|
|
|
|
if (BuildCache != null)
|
|
{
|
|
try
|
|
{
|
|
BuildCache.Refresh(compiled.OriginalBuildItem, outputFilename);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
BuildLogger.LogWarning(null, new ContentIdentity() { SourceTool = "BuildCache" }, exc.Message);
|
|
Debugger.Break();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BuildLogger.LogMessage("---Build of \"{0}\" not successfull.", buildItem.AssetName);
|
|
}
|
|
}
|
|
|
|
return result.ToArray();
|
|
}
|
|
|
|
private string MakeAbsolute(string filename)
|
|
{
|
|
if (filename == null || BaseDirectory == null)
|
|
return filename;
|
|
|
|
var uri = new Uri(filename, UriKind.RelativeOrAbsolute);
|
|
|
|
if (uri.IsAbsoluteUri)
|
|
return filename;
|
|
|
|
return new Uri(BaseDirectory, uri).OriginalString;
|
|
}
|
|
|
|
private object ImportAsset(BuildItem item, string absoluteFilename, ContentImporterContext context)
|
|
{
|
|
IContentImporter importer = this.ImporterManager.GetInstance(item.ImporterName);
|
|
buildLogger.LogMessage("importing {0} with importer {1}", item.SourceFilename.ToString(), importer.GetType());
|
|
|
|
object result = importer.Import(absoluteFilename, context);
|
|
|
|
if (result == null)
|
|
{
|
|
var identity = new ContentIdentity();
|
|
identity.SourceFilename = absoluteFilename;
|
|
|
|
buildLogger.LogWarning("", identity, "importer \"{0}\" didn't return a value.", item.ImporterName);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private object Process(BuildItem item, string absoluteFilename, object importedObject, ContentProcessorContext context)
|
|
{
|
|
IContentProcessor instance = this.ProcessorManager.GetInstance(item.ProcessorName);
|
|
SetProcessorParameters(instance, item.ProcessorParameters);
|
|
|
|
buildLogger.LogMessage("building with processor {0}", instance.GetType());
|
|
|
|
if (!instance.InputType.IsAssignableFrom(importedObject.GetType()))
|
|
{
|
|
ContentIdentity identity = null;
|
|
if (importedObject is ContentItem)
|
|
{
|
|
identity = ((ContentItem)importedObject).Identity;
|
|
}
|
|
|
|
if (identity == null)
|
|
{
|
|
identity = new ContentIdentity();
|
|
identity.SourceFilename = absoluteFilename;
|
|
}
|
|
|
|
buildLogger.LogWarning("", identity, "The input type of the processor \"{0}\" is not assignable from the output type of the importer \"{1}\".", item.ProcessorName, item.ImporterName);
|
|
return null;
|
|
}
|
|
|
|
return instance.Process(importedObject, context);
|
|
}
|
|
|
|
private void SerializeAsset(BuildItem item, object assetData, string outputDirectory, string outputFilename)
|
|
{
|
|
string dir =Path.GetDirectoryName(outputFilename);
|
|
if (!Directory.Exists(dir))
|
|
Directory.CreateDirectory(dir);
|
|
|
|
buildLogger.LogMessage("serializing {0}", new object[] { item.AssetName });
|
|
using (Stream stream = new FileStream(outputFilename, FileMode.Create, FileAccess.Write, FileShare.None))
|
|
{
|
|
this.ContentCompiler.Compile(stream, assetData, TargetPlatform, TargetProfile, CompressContent, outputDirectory, outputFilename);
|
|
}
|
|
}
|
|
|
|
private void SetProcessorParameters(IContentProcessor instance, IDictionary<string, object> parameters)
|
|
{
|
|
if (instance == null)
|
|
{
|
|
throw new ArgumentNullException("instance");
|
|
}
|
|
|
|
if (parameters == null)
|
|
{
|
|
throw new ArgumentNullException("parameters");
|
|
}
|
|
|
|
if (parameters.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Type processorType = instance.GetType();
|
|
|
|
foreach (var keyPair in parameters)
|
|
{
|
|
var property = processorType.GetProperty(keyPair.Key);
|
|
if (property != null)
|
|
{
|
|
if (keyPair.Value != null && property.PropertyType != keyPair.Value.GetType())
|
|
{
|
|
TypeConverter converter = property.GetConverter();
|
|
if (converter == null)
|
|
continue;
|
|
|
|
object value;
|
|
try
|
|
{
|
|
value = converter.ConvertFrom(keyPair.Value);
|
|
}
|
|
catch (NotSupportedException exc)
|
|
{
|
|
throw new ContentLoadException(string.Format("Unable to convert processor parameter \"{0}\" from {1} to {2}.", keyPair.Key, keyPair.Value.GetType(), property.PropertyType), exc);
|
|
}
|
|
|
|
property.SetValue(instance, value, null);
|
|
}
|
|
else
|
|
{
|
|
property.SetValue(instance, keyPair.Value, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LogException(Exception exc, string filename)
|
|
{
|
|
var identity = new ContentIdentity();
|
|
identity.SourceFilename = filename;
|
|
|
|
buildLogger.LogWarning(exc.HelpLink, identity, exc.Message);
|
|
}
|
|
}
|
|
}
|