Object pools for style runs, font runs and text lines

This commit is contained in:
Brad Robinson 2019-08-08 09:45:58 +10:00
Родитель c65503fe06
Коммит db07829b0c
9 изменённых файлов: 251 добавлений и 66 удалений

Просмотреть файл

@ -7,7 +7,8 @@
{
//BidiTest.Run();
//BidiCharacterTest.Run();
LineBreakTest.Run();
//LineBreakTest.Run();
TextBlockLoadTest.Run();
}
}
}

Просмотреть файл

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Text;
using Topten.RichTextKit;
namespace TestBench
{
public static class TextBlockLoadTest
{
public static bool Run()
{
Console.WriteLine("Text Block Load Test");
Console.WriteLine("--------------------");
Console.WriteLine();
string typefaceName = "Arial";
float Scale = 1.5f;
var styleSmall = new Style() { FontFamily = typefaceName, FontSize = 12 * Scale };
var styleScript = new Style() { FontFamily = "Segoe Script", FontSize = 18 * Scale };
var styleHeading = new Style() { FontFamily = typefaceName, FontSize = 24 * Scale, FontWeight = 700 };
var styleNormal = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, LineHeight = 1.0f };
var styleBold = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, FontWeight = 700 };
var styleUnderline = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, Underline = UnderlineStyle.Gapped };
var styleStrike = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, StrikeThrough = StrikeThroughStyle.Solid };
var styleSubScript = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, FontVariant = FontVariant.SubScript };
var styleSuperScript = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale, FontVariant = FontVariant.SuperScript };
var styleItalic = new Style() { FontFamily = typefaceName, FontItalic = true, FontSize = 18 * Scale };
var styleBoldLarge = new Style() { FontFamily = typefaceName, FontSize = 28 * Scale, FontWeight = 700 };
var styleRed = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale/*, TextColor = new SKColor(0xFFFF0000) */};
var styleBlue = new Style() { FontFamily = typefaceName, FontSize = 18 * Scale/*, TextColor = new SKColor(0xFF0000FF) */};
var tr = new TestResults();
var tb = new TextBlock();
for (int i = 0; i < 1000; i++)
{
tr.EnterTest();
tb.Clear();
tb.MaxWidth = 1000;
tb.AddText("Welcome to RichTextKit!\n", styleHeading);
tb.AddText("\nRichTextKit is a rich text layout, rendering and measurement library for SkiaSharp.\n\nIt supports normal, ", styleNormal);
tb.AddText("bold", styleBold);
tb.AddText(", ", styleNormal);
tb.AddText("italic", styleItalic);
tb.AddText(", ", styleNormal);
tb.AddText("underline", styleUnderline);
tb.AddText(" (including ", styleNormal);
tb.AddText("gaps over descenders", styleUnderline);
tb.AddText("), ", styleNormal);
tb.AddText("strikethrough", styleStrike);
tb.AddText(", superscript (E=mc", styleNormal);
tb.AddText("2", styleSuperScript);
tb.AddText("), subscript (H", styleNormal);
tb.AddText("2", styleSubScript);
tb.AddText("O), ", styleNormal);
tb.AddText("colored ", styleRed);
tb.AddText("text", styleBlue);
tb.AddText(" and ", styleNormal);
tb.AddText("mixed ", styleNormal);
tb.AddText("sizes", styleSmall);
tb.AddText(" and ", styleNormal);
tb.AddText("fonts", styleScript);
tb.AddText(".\n\n", styleNormal);
tb.AddText("Font fallback means emojis work: 🌐 🍪 🍕 🚀 and ", styleNormal);
tb.AddText("text shaping and bi-directional text support means complex scripts and languages like Arabic: مرحبا بالعالم, Japanese: ハローワールド, Chinese: 世界您好 and Hindi: हैलो वर्ल्ड are rendered correctly!\n\n", styleNormal);
tb.AddText("RichTextKit also supports left/center/right text alignment, word wrapping, truncation with ellipsis place-holder, text measurement, hit testing, painting a selection range, caret position & shape helpers.", styleNormal);
tb.Layout();
tr.LeaveTest();
tr.TestPassed(true);
}
tr.Dump();
return tr.AllPassed;
}
}
}

Просмотреть файл

@ -304,23 +304,21 @@ namespace Topten.RichTextKit
}
// Create the other run
var newRun = new FontRun()
{
StyleRun = this.StyleRun,
CodePointBuffer = this.CodePointBuffer,
Direction = this.Direction,
Ascent = this.Ascent,
Descent = this.Descent,
Style = this.Style,
Typeface = this.Typeface,
Start = splitAtCodePoint,
Length = this.End - splitAtCodePoint,
Width = sliceRightWidth,
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos),
GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos),
Glyphs = this.Glyphs.SubSlice(glyphSplitPos),
Clusters = this.Clusters.SubSlice(glyphSplitPos),
};
var newRun = FontRun.Pool.Get();
newRun.StyleRun = this.StyleRun;
newRun.CodePointBuffer = this.CodePointBuffer;
newRun.Direction = this.Direction;
newRun.Ascent = this.Ascent;
newRun.Descent = this.Descent;
newRun.Style = this.Style;
newRun.Typeface = this.Typeface;
newRun.Start = splitAtCodePoint;
newRun.Length = this.End - splitAtCodePoint;
newRun.Width = sliceRightWidth;
newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
newRun.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
newRun.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
newRun.Clusters = this.Clusters.SubSlice(glyphSplitPos);
// Adjust code point positions
for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++)
@ -375,23 +373,21 @@ namespace Topten.RichTextKit
}
// Create the other run
var newRun = new FontRun()
{
StyleRun = this.StyleRun,
CodePointBuffer = this.CodePointBuffer,
Direction = this.Direction,
Ascent = this.Ascent,
Descent = this.Descent,
Style = this.Style,
Typeface = this.Typeface,
Start = splitAtCodePoint,
Length = this.End - splitAtCodePoint,
Width = sliceLeftWidth,
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos),
GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos),
Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos),
Clusters = this.Clusters.SubSlice(0, glyphSplitPos),
};
var newRun = FontRun.Pool.Get();
newRun.StyleRun = this.StyleRun;
newRun.CodePointBuffer = this.CodePointBuffer;
newRun.Direction = this.Direction;
newRun.Ascent = this.Ascent;
newRun.Descent = this.Descent;
newRun.Style = this.Style;
newRun.Typeface = this.Typeface;
newRun.Start = splitAtCodePoint;
newRun.Length = this.End - splitAtCodePoint;
newRun.Width = sliceLeftWidth;
newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
newRun.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
newRun.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
newRun.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
// Update this run
this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos);
@ -592,5 +588,18 @@ namespace Topten.RichTextKit
}
}
}
[ThreadStatic]
internal static ObjectPool<FontRun> Pool = new ObjectPool<FontRun>()
{
Cleaner = (r) =>
{
r.RunKind = FontRunKind.Normal;
r.CodePointBuffer = null;
r.Style = null;
r.Typeface = null;
r.Line = null;
}
};
}
}

Просмотреть файл

@ -64,5 +64,16 @@ namespace Topten.RichTextKit
/// The global list of code points
/// </summary>
internal Buffer<int> CodePointBuffer;
[ThreadStatic]
internal static ObjectPool<StyleRun> Pool = new ObjectPool<StyleRun>()
{
Cleaner = (r) =>
{
r.CodePointBuffer = null;
r.Style = null;
r.TextBlock = null;
}
};
}
}

Просмотреть файл

@ -157,9 +157,9 @@ namespace Topten.RichTextKit
{
// Reset everything
_codePoints.Clear();
_styleRuns.Clear();
_fontRuns.Clear();
_lines.Clear();
StyleRun.Pool.ReturnAndClear(_styleRuns);
FontRun.Pool.ReturnAndClear(_fontRuns);
TextLine.Pool.ReturnAndClear(_lines);
_textShapingBuffers.Clear();
InvalidateLayout();
}
@ -179,14 +179,12 @@ namespace Topten.RichTextKit
var utf32 = _codePoints.Add(text);
// Create a run
var run = new StyleRun()
{
TextBlock = this,
CodePointBuffer = _codePoints,
Start = utf32.Start,
Length = utf32.Length,
Style = style,
};
var run = StyleRun.Pool.Get();
run.TextBlock = this;
run.CodePointBuffer = _codePoints;
run.Start = utf32.Start;
run.Length = utf32.Length;
run.Style = style;
// Add run
_styleRuns.Add(run);
@ -194,6 +192,8 @@ namespace Topten.RichTextKit
return run;
}
List<StyleRun> _styledRunPool = new List<StyleRun>();
/// <summary>
/// Add text to this paragraph
/// </summary>
@ -799,7 +799,8 @@ namespace Topten.RichTextKit
return _textAlignment;
}
Bidi _bidi = new Bidi();
// Use the shared Bidi algo instance
Bidi _bidi = Bidi.Instance;
/// <summary>
/// Split into runs based on directionality and style switch points
@ -943,23 +944,22 @@ namespace Topten.RichTextKit
}
// Create the run
return new FontRun()
{
StyleRun = styleRun,
CodePointBuffer = _codePoints,
Start = codePoints.Start,
Length = codePoints.Length,
Style = style,
Direction = direction,
Typeface = typeface,
Glyphs = shaped.GlyphIndicies,
GlyphPositions = shaped.GlyphPositions,
RelativeCodePointXCoords = shaped.CodePointXCoords,
Clusters = shaped.Clusters,
Ascent = shaped.Ascent,
Descent = shaped.Descent,
Width = shaped.EndXCoord.X,
};
var fontRun = FontRun.Pool.Get();
fontRun.StyleRun = styleRun;
fontRun.CodePointBuffer = _codePoints;
fontRun.Start = codePoints.Start;
fontRun.Length = codePoints.Length;
fontRun.Style = style;
fontRun.Direction = direction;
fontRun.Typeface = typeface;
fontRun.Glyphs = shaped.GlyphIndicies;
fontRun.GlyphPositions = shaped.GlyphPositions;
fontRun.RelativeCodePointXCoords = shaped.CodePointXCoords;
fontRun.Clusters = shaped.Clusters;
fontRun.Ascent = shaped.Ascent;
fontRun.Descent = shaped.Descent;
fontRun.Width = shaped.EndXCoord.X;
return fontRun;
}
/// <summary>
@ -1110,7 +1110,7 @@ namespace Topten.RichTextKit
void BuildLine(int frIndexStartOfLine, int frSplitIndex, int frTrailingWhiteSpaceIndex)
{
// Create the line
var line = new TextLine();
var line = TextLine.Pool.Get();
line.TextBlock = this;
line.YCoord = _measuredHeight;

Просмотреть файл

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Topten.RichTextKit.Utils;
namespace Topten.RichTextKit
{
@ -296,5 +297,15 @@ namespace Topten.RichTextKit
/// Internal List of runs
/// </summary>
internal List<FontRun> RunsInternal = new List<FontRun>();
[ThreadStatic]
internal static ObjectPool<TextLine> Pool = new ObjectPool<TextLine>()
{
Cleaner = (r) =>
{
r.TextBlock = null;
r.RunsInternal.Clear();
}
};
}
}

Просмотреть файл

@ -202,6 +202,7 @@ namespace Topten.RichTextKit.Utils
return new ArraySliceEnumerator<T>(_data, 0, _length);
}
/*
public T[] SetMapping(Slice<T> data, Slice<int> mapping)
{
Length = mapping.Length;
@ -221,5 +222,6 @@ namespace Topten.RichTextKit.Utils
}
return _data;
}
*/
}
}

Просмотреть файл

@ -0,0 +1,73 @@
//#define NO_POOLING
using System;
using System.Collections.Generic;
using System.Text;
namespace Topten.RichTextKit.Utils
{
internal class ObjectPool<T> where T : class, new()
{
public ObjectPool()
{
}
public T Get()
{
#if NO_POOLING
return new T();
#else
int count = _pool.Count;
if (count == 0)
return new T();
var obj = _pool[count - 1];
_pool.RemoveAt(count - 1);
return obj;
#endif
}
public void Return(T obj)
{
#if NO_POOLING
#else
Cleaner?.Invoke(obj);
_pool.Add(obj);
#endif
}
public void Return(IEnumerable<T> objs)
{
#if NO_POOLING
#else
if (Cleaner != null)
{
foreach (var x in objs)
{
Cleaner(x);
}
_pool.AddRange(objs);
}
#endif
}
public void ReturnAndClear(List<T> objs)
{
#if NO_POOLING
#else
if (Cleaner != null)
{
foreach (var x in objs)
{
Cleaner(x);
}
_pool.AddRange(objs);
}
#endif
objs.Clear();
}
public Action<T> Cleaner;
List<T> _pool = new List<T>();
}
}

Просмотреть файл

@ -31,7 +31,7 @@ if (bt.options.official)
bt.git_tag();
// Push nuget package
//bt.nupush(`./build/Release/Topten.RichTextKit/*.${bt.options.version.build}.nupkg`, "http://nuget.toptensoftware.com/v3/index.json");
bt.nupush(`./build/Release/Topten.RichTextKit/*.${bt.options.version.build}.nupkg`, "http://nuget.toptensoftware.com/v3/index.json");
}