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 System.Globalization;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace mustache.test namespace mustache.test
{ {
@ -901,19 +902,14 @@ Last";
{ {
} }
protected override bool GetIsContextSensitive()
{
return false;
}
protected override IEnumerable<TagParameter> GetParameters() protected override IEnumerable<TagParameter> GetParameters()
{ {
return new TagParameter[] { new TagParameter("param") { IsRequired = false, DefaultValue = 123 } }; 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("0.0.1.0")] [assembly: AssemblyVersion("0.0.2.0")]
[assembly: AssemblyFileVersion("0.0.1.0")] [assembly: AssemblyFileVersion("0.0.2.0")]

View File

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.IO;
namespace mustache 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); 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; LinkedList<IGenerator> generators;
if (_definition.ShouldGeneratePrimaryGroup(arguments)) if (_definition.ShouldGeneratePrimaryGroup(arguments))
{ {
@ -90,16 +89,17 @@ namespace mustache
generators.AddLast(_subGenerator); generators.AddLast(_subGenerator);
} }
} }
foreach (KeyScope childScope in scopes) foreach (NestedContext context in contexts)
{ {
foreach (IGenerator generator in generators) 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;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace mustache namespace mustache
{ {
@ -38,12 +39,13 @@ namespace mustache
} }
/// <summary> /// <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> /// </summary>
/// <param name="writer">The text writer passed</param>
/// <param name="scope">The current scope.</param> /// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments passed to the tag.</param> /// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The scopes for each of the items found in the argument.</returns> /// <returns>The scope to use when building the inner text of the tag.</returns>
public override IEnumerable<KeyScope> GetChildScopes(KeyScope scope, Dictionary<string, object> arguments) public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
{ {
object value = arguments[collectionParameter]; object value = arguments[collectionParameter];
IEnumerable enumerable = value as IEnumerable; IEnumerable enumerable = value as IEnumerable;
@ -53,7 +55,7 @@ namespace mustache
} }
foreach (object item in enumerable) 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;
using System.Globalization; using System.Globalization;
using System.IO;
namespace mustache namespace mustache
{ {
@ -47,7 +48,9 @@ namespace mustache
private string render(IFormatProvider provider, object source) private string render(IFormatProvider provider, object source)
{ {
KeyScope scope = new KeyScope(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;
using System.IO;
namespace mustache namespace mustache
{ {
@ -10,9 +11,9 @@ namespace mustache
/// <summary> /// <summary>
/// Generates the text when applying the format plan. /// Generates the text when applying the format plan.
/// </summary> /// </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="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> /// <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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace mustache namespace mustache
{ {
@ -22,10 +23,10 @@ namespace mustache
_arguments = arguments; _arguments = arguments;
} }
string IGenerator.GetText(IFormatProvider provider, KeyScope scope) void IGenerator.GetText(KeyScope scope, TextWriter writer)
{ {
Dictionary<string, object> arguments = _arguments.GetArguments(scope); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace mustache namespace mustache
{ {
@ -35,25 +36,5 @@ namespace mustache
{ {
return false; 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;
using System.IO;
using System.Text; using System.Text;
namespace mustache namespace mustache
@ -41,10 +42,10 @@ namespace mustache
return formatBuilder.ToString(); return formatBuilder.ToString();
} }
string IGenerator.GetText(IFormatProvider provider, KeyScope scope) void IGenerator.GetText(KeyScope scope, TextWriter writer)
{ {
object value = scope.Find(_key); 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.0.1.0")] [assembly: AssemblyVersion("0.0.2.0")]
[assembly: AssemblyFileVersion("0.0.1.0")] [assembly: AssemblyFileVersion("0.0.2.0")]
[assembly: InternalsVisibleTo("mustache-sharp.test")] [assembly: InternalsVisibleTo("mustache-sharp.test")]

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace mustache 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using mustache.Properties; using mustache.Properties;
using System.IO;
namespace mustache namespace mustache
{ {
@ -130,26 +130,35 @@ namespace mustache
} }
/// <summary> /// <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> /// </summary>
/// <param name="writer">The text writer passed</param>
/// <param name="scope">The current scope.</param> /// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments passed to the tag.</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> /// <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> /// <summary>
/// Applies additional formatting to the inner text of the tag. /// Applies additional formatting to the inner text of the tag.
/// </summary> /// </summary>
/// <param name="provider">The format provider to use.</param> /// <param name="writer">The text writer to write to.</param>
/// <param name="innerText">The inner text of the tag.</param>
/// <param name="arguments">The arguments passed to the tag.</param> /// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The decorated inner text.</returns> public virtual void GetText(TextWriter writer, Dictionary<string, object> arguments)
public virtual string Decorate(IFormatProvider provider, string innerText, 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> /// <summary>

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace mustache namespace mustache
{ {
@ -36,15 +37,16 @@ namespace mustache
} }
/// <summary> /// <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> /// </summary>
/// <param name="writer">The text writer passed</param>
/// <param name="scope">The current scope.</param> /// <param name="scope">The current scope.</param>
/// <param name="arguments">The arguments that were passed to the tag.</param> /// <param name="arguments">The arguments passed to the tag.</param>
/// <returns>The scopes to use for generating the tag's content.</returns> /// <returns>The scope to use when building the inner text of the tag.</returns>
public override IEnumerable<KeyScope> GetChildScopes(KeyScope scope, Dictionary<string, object> arguments) public override IEnumerable<NestedContext> GetChildContext(TextWriter writer, KeyScope scope, Dictionary<string, object> arguments)
{ {
object context = arguments[contextParameter]; 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="InlineGenerator.cs" />
<Compile Include="KeyGenerator.cs" /> <Compile Include="KeyGenerator.cs" />
<Compile Include="MasterTagDefinition.cs" /> <Compile Include="MasterTagDefinition.cs" />
<Compile Include="NestedContext.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>