Optimized text generation.

The way the code was implemented before, each block of text was
generating a string which was then being added to a StringBuilder. This
only improved performance within a block itself. Needing to then copy
the results of that builder into the parent tag's builder was wasteful.
Now, a single TextWriter is used for all tags. If a block needs to be
processed after-the-fact, the tag can indicate that it wants to provide
a new text writer and that it wants to consolidate the text.
This commit is contained in:
Travis Parks 2013-01-16 15:10:25 -05:00
parent 83b2a8a3d9
commit 790f856b44
15 changed files with 114 additions and 67 deletions

View File

@ -2,6 +2,7 @@
using System.Globalization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.IO;
namespace mustache.test
{
@ -901,19 +902,14 @@ Last";
{
}
protected override bool GetIsContextSensitive()
{
return false;
}
protected override IEnumerable<TagParameter> GetParameters()
{
return new TagParameter[] { new TagParameter("param") { IsRequired = false, DefaultValue = 123 } };
}
protected override string GetText(IFormatProvider provider, Dictionary<string, object> arguments)
public override void GetText(TextWriter writer, Dictionary<string, object> arguments)
{
return arguments["param"].ToString();
writer.Write(arguments["param"]);
}
}

View File

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("0.0.1.0")]
[assembly: AssemblyFileVersion("0.0.1.0")]
[assembly: AssemblyVersion("0.0.2.0")]
[assembly: AssemblyFileVersion("0.0.2.0")]

View File

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace mustache
{
@ -72,11 +72,10 @@ namespace mustache
}
}
string IGenerator.GetText(IFormatProvider provider, KeyScope scope)
void IGenerator.GetText(KeyScope scope, TextWriter writer)
{
StringBuilder builder = new StringBuilder();
Dictionary<string, object> arguments = _arguments.GetArguments(scope);
IEnumerable<KeyScope> scopes = _definition.GetChildScopes(scope, arguments);
IEnumerable<NestedContext> contexts = _definition.GetChildContext(writer, scope, arguments);
LinkedList<IGenerator> generators;
if (_definition.ShouldGeneratePrimaryGroup(arguments))
{
@ -90,16 +89,17 @@ namespace mustache
generators.AddLast(_subGenerator);
}
}
foreach (KeyScope childScope in scopes)
foreach (NestedContext context in contexts)
{
foreach (IGenerator generator in generators)
{
builder.Append(generator.GetText(provider, childScope));
generator.GetText(context.KeyScope, context.Writer);
if (context.WriterNeedsConsidated)
{
writer.Write(_definition.ConsolidateWriter(context.Writer, arguments));
}
}
}
string innerText = builder.ToString();
string outerText = _definition.Decorate(provider, innerText, arguments);
return outerText;
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace mustache
{
@ -38,12 +39,13 @@ namespace mustache
}
/// <summary>
/// Gets the scopes for each of the items found in the argument.
/// Gets the context to use when building the inner text of the tag.
/// </summary>
/// <param name="writer">The text writer passed</param>
/// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The scopes for each of the items found in the argument.</returns>
public override IEnumerable<KeyScope> GetChildScopes(KeyScope scope, Dictionary<string, object> arguments)
/// <returns>The scope to use when building the inner text of the tag.</returns>
public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
{
object value = arguments[collectionParameter];
IEnumerable enumerable = value as IEnumerable;
@ -53,7 +55,7 @@ namespace mustache
}
foreach (object item in enumerable)
{
yield return scope.CreateChildScope(item);
yield return new NestedContext() { KeyScope = scope.CreateChildScope(item), Writer = writer };
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Globalization;
using System.IO;
namespace mustache
{
@ -47,7 +48,9 @@ namespace mustache
private string render(IFormatProvider provider, object source)
{
KeyScope scope = new KeyScope(source);
return _generator.GetText(provider, scope);
StringWriter writer = new StringWriter(provider);
_generator.GetText(scope, writer);
return writer.ToString();
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.IO;
namespace mustache
{
@ -10,9 +11,9 @@ namespace mustache
/// <summary>
/// Generates the text when applying the format plan.
/// </summary>
/// <param name="provider">The format provider to use when formatting the keys.</param>
/// <param name="scope">The current lexical scope of the keys.</param>
/// <param name="writer">The text writer to send all text to.</param>
/// <returns>The generated text.</returns>
string GetText(IFormatProvider provider, KeyScope scope);
void GetText(KeyScope scope, TextWriter writer);
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace mustache
{
@ -22,10 +23,10 @@ namespace mustache
_arguments = arguments;
}
string IGenerator.GetText(IFormatProvider provider, KeyScope scope)
void IGenerator.GetText(KeyScope scope, TextWriter writer)
{
Dictionary<string, object> arguments = _arguments.GetArguments(scope);
return _definition.Decorate(provider, String.Empty, arguments);
_definition.GetText(writer, arguments);
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace mustache
{
@ -35,25 +36,5 @@ namespace mustache
{
return false;
}
/// <summary>
/// Generates the text for the tag.
/// </summary>
/// <param name="provider">The format provider to use.</param>
/// <param name="innerText">The text to decorate. This will always be an empty string.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The generated text.</returns>
public sealed override string Decorate(IFormatProvider provider, string innerText, Dictionary<string, object> arguments)
{
return GetText(provider, arguments);
}
/// <summary>
/// Gets the text of the inline tag.
/// </summary>
/// <param name="provider">The format provider to use.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The generated text.</returns>
protected abstract string GetText(IFormatProvider provider, Dictionary<string, object> arguments);
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Text;
namespace mustache
@ -41,10 +42,10 @@ namespace mustache
return formatBuilder.ToString();
}
string IGenerator.GetText(IFormatProvider provider, KeyScope scope)
void IGenerator.GetText(KeyScope scope, TextWriter writer)
{
object value = scope.Find(_key);
return String.Format(provider, _format, value);
writer.Write(_format, value);
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.IO;
namespace mustache
{
/// <summary>
/// Holds the objects to use when processing a child context of another tag.
/// </summary>
public sealed class NestedContext
{
/// <summary>
/// Initializes a new instance of a NestedContext.
/// </summary>
public NestedContext()
{
}
/// <summary>
/// Gets or sets the writer to use when generating the child context.
/// </summary>
/// <remarks>Setting the writer to null will indicate that the tag's writer should be used.</remarks>
public TextWriter Writer
{
get;
set;
}
/// <summary>
/// Gets or sets whether the text sent to the returned writer needs to be added
/// to the parent tag's writer. This should be false if the parent writer is
/// being returned or is being wrapped.
/// </summary>
public bool WriterNeedsConsidated
{
get;
set;
}
/// <summary>
/// Gets or sets the scope to use when generating the child context.
/// </summary>
/// <remarks>Setting the scope to null will indicate that the current scope should be used.</remarks>
public KeyScope KeyScope
{
get;
set;
}
}
}

View File

@ -34,6 +34,6 @@ using System.Runtime.CompilerServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.0.1.0")]
[assembly: AssemblyFileVersion("0.0.1.0")]
[assembly: AssemblyVersion("0.0.2.0")]
[assembly: AssemblyFileVersion("0.0.2.0")]
[assembly: InternalsVisibleTo("mustache-sharp.test")]

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace mustache
{
@ -45,9 +46,9 @@ namespace mustache
}
}
string IGenerator.GetText(IFormatProvider provider, KeyScope scope)
void IGenerator.GetText(KeyScope scope, TextWriter writer)
{
return Value;
writer.Write(Value);
}
}
}

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using mustache.Properties;
using System.IO;
namespace mustache
{
@ -130,26 +130,35 @@ namespace mustache
}
/// <summary>
/// Gets the scope to use when building the inner text of the tag.
/// Gets the context to use when building the inner text of the tag.
/// </summary>
/// <param name="writer">The text writer passed</param>
/// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The scope to use when building the inner text of the tag.</returns>
public virtual IEnumerable<KeyScope> GetChildScopes(KeyScope scope, Dictionary<string, object> arguments)
public virtual IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
{
yield return scope;
yield return new NestedContext() { KeyScope = scope, Writer = writer };
}
/// <summary>
/// Applies additional formatting to the inner text of the tag.
/// </summary>
/// <param name="provider">The format provider to use.</param>
/// <param name="innerText">The inner text of the tag.</param>
/// <param name="writer">The text writer to write to.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The decorated inner text.</returns>
public virtual string Decorate(IFormatProvider provider, string innerText, Dictionary<string, object> arguments)
public virtual void GetText(TextWriter writer, Dictionary<string, object> arguments)
{
return innerText;
}
/// <summary>
/// Consolidates the text in the given writer to a string, using the given arguments as necessary.
/// </summary>
/// <param name="writer">The writer containing the text to consolidate.</param>
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The consolidated string.</returns>
public virtual string ConsolidateWriter(TextWriter writer, Dictionary<string, object> arguments)
{
return writer.ToString();
}
/// <summary>

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace mustache
{
@ -36,15 +37,16 @@ namespace mustache
}
/// <summary>
/// Gets the scopes to use for generating the tag's content.
/// Gets the context to use when building the inner text of the tag.
/// </summary>
/// <param name="writer">The text writer passed</param>
/// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments that were passed to the tag.</param>
/// <returns>The scopes to use for generating the tag's content.</returns>
public override IEnumerable<KeyScope> GetChildScopes(KeyScope scope, Dictionary<string, object> arguments)
/// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The scope to use when building the inner text of the tag.</returns>
public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
{
object context = arguments[contextParameter];
yield return scope.CreateChildScope(context);
yield return new NestedContext() { KeyScope = scope.CreateChildScope(context), Writer = writer };
}
}
}

View File

@ -49,6 +49,7 @@
<Compile Include="InlineGenerator.cs" />
<Compile Include="KeyGenerator.cs" />
<Compile Include="MasterTagDefinition.cs" />
<Compile Include="NestedContext.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>