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(); //BidiTest.Run();
//BidiCharacterTest.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 // Create the other run
var newRun = new FontRun() var newRun = FontRun.Pool.Get();
{ newRun.StyleRun = this.StyleRun;
StyleRun = this.StyleRun, newRun.CodePointBuffer = this.CodePointBuffer;
CodePointBuffer = this.CodePointBuffer, newRun.Direction = this.Direction;
Direction = this.Direction, newRun.Ascent = this.Ascent;
Ascent = this.Ascent, newRun.Descent = this.Descent;
Descent = this.Descent, newRun.Style = this.Style;
Style = this.Style, newRun.Typeface = this.Typeface;
Typeface = this.Typeface, newRun.Start = splitAtCodePoint;
Start = splitAtCodePoint, newRun.Length = this.End - splitAtCodePoint;
Length = this.End - splitAtCodePoint, newRun.Width = sliceRightWidth;
Width = sliceRightWidth, newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos), newRun.GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos);
GlyphPositions = this.GlyphPositions.SubSlice(glyphSplitPos), newRun.Glyphs = this.Glyphs.SubSlice(glyphSplitPos);
Glyphs = this.Glyphs.SubSlice(glyphSplitPos), newRun.Clusters = this.Clusters.SubSlice(glyphSplitPos);
Clusters = this.Clusters.SubSlice(glyphSplitPos),
};
// Adjust code point positions // Adjust code point positions
for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++) for (int i = 0; i < newRun.RelativeCodePointXCoords.Length; i++)
@ -375,23 +373,21 @@ namespace Topten.RichTextKit
} }
// Create the other run // Create the other run
var newRun = new FontRun() var newRun = FontRun.Pool.Get();
{ newRun.StyleRun = this.StyleRun;
StyleRun = this.StyleRun, newRun.CodePointBuffer = this.CodePointBuffer;
CodePointBuffer = this.CodePointBuffer, newRun.Direction = this.Direction;
Direction = this.Direction, newRun.Ascent = this.Ascent;
Ascent = this.Ascent, newRun.Descent = this.Descent;
Descent = this.Descent, newRun.Style = this.Style;
Style = this.Style, newRun.Typeface = this.Typeface;
Typeface = this.Typeface, newRun.Start = splitAtCodePoint;
Start = splitAtCodePoint, newRun.Length = this.End - splitAtCodePoint;
Length = this.End - splitAtCodePoint, newRun.Width = sliceLeftWidth;
Width = sliceLeftWidth, newRun.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos);
RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(codePointSplitPos), newRun.GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos);
GlyphPositions = this.GlyphPositions.SubSlice(0, glyphSplitPos), newRun.Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos);
Glyphs = this.Glyphs.SubSlice(0, glyphSplitPos), newRun.Clusters = this.Clusters.SubSlice(0, glyphSplitPos);
Clusters = this.Clusters.SubSlice(0, glyphSplitPos),
};
// Update this run // Update this run
this.RelativeCodePointXCoords = this.RelativeCodePointXCoords.SubSlice(0, codePointSplitPos); 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 /// The global list of code points
/// </summary> /// </summary>
internal Buffer<int> CodePointBuffer; 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 // Reset everything
_codePoints.Clear(); _codePoints.Clear();
_styleRuns.Clear(); StyleRun.Pool.ReturnAndClear(_styleRuns);
_fontRuns.Clear(); FontRun.Pool.ReturnAndClear(_fontRuns);
_lines.Clear(); TextLine.Pool.ReturnAndClear(_lines);
_textShapingBuffers.Clear(); _textShapingBuffers.Clear();
InvalidateLayout(); InvalidateLayout();
} }
@ -179,14 +179,12 @@ namespace Topten.RichTextKit
var utf32 = _codePoints.Add(text); var utf32 = _codePoints.Add(text);
// Create a run // Create a run
var run = new StyleRun() var run = StyleRun.Pool.Get();
{ run.TextBlock = this;
TextBlock = this, run.CodePointBuffer = _codePoints;
CodePointBuffer = _codePoints, run.Start = utf32.Start;
Start = utf32.Start, run.Length = utf32.Length;
Length = utf32.Length, run.Style = style;
Style = style,
};
// Add run // Add run
_styleRuns.Add(run); _styleRuns.Add(run);
@ -194,6 +192,8 @@ namespace Topten.RichTextKit
return run; return run;
} }
List<StyleRun> _styledRunPool = new List<StyleRun>();
/// <summary> /// <summary>
/// Add text to this paragraph /// Add text to this paragraph
/// </summary> /// </summary>
@ -799,7 +799,8 @@ namespace Topten.RichTextKit
return _textAlignment; return _textAlignment;
} }
Bidi _bidi = new Bidi(); // Use the shared Bidi algo instance
Bidi _bidi = Bidi.Instance;
/// <summary> /// <summary>
/// Split into runs based on directionality and style switch points /// Split into runs based on directionality and style switch points
@ -943,23 +944,22 @@ namespace Topten.RichTextKit
} }
// Create the run // Create the run
return new FontRun() var fontRun = FontRun.Pool.Get();
{ fontRun.StyleRun = styleRun;
StyleRun = styleRun, fontRun.CodePointBuffer = _codePoints;
CodePointBuffer = _codePoints, fontRun.Start = codePoints.Start;
Start = codePoints.Start, fontRun.Length = codePoints.Length;
Length = codePoints.Length, fontRun.Style = style;
Style = style, fontRun.Direction = direction;
Direction = direction, fontRun.Typeface = typeface;
Typeface = typeface, fontRun.Glyphs = shaped.GlyphIndicies;
Glyphs = shaped.GlyphIndicies, fontRun.GlyphPositions = shaped.GlyphPositions;
GlyphPositions = shaped.GlyphPositions, fontRun.RelativeCodePointXCoords = shaped.CodePointXCoords;
RelativeCodePointXCoords = shaped.CodePointXCoords, fontRun.Clusters = shaped.Clusters;
Clusters = shaped.Clusters, fontRun.Ascent = shaped.Ascent;
Ascent = shaped.Ascent, fontRun.Descent = shaped.Descent;
Descent = shaped.Descent, fontRun.Width = shaped.EndXCoord.X;
Width = shaped.EndXCoord.X, return fontRun;
};
} }
/// <summary> /// <summary>
@ -1110,7 +1110,7 @@ namespace Topten.RichTextKit
void BuildLine(int frIndexStartOfLine, int frSplitIndex, int frTrailingWhiteSpaceIndex) void BuildLine(int frIndexStartOfLine, int frSplitIndex, int frTrailingWhiteSpaceIndex)
{ {
// Create the line // Create the line
var line = new TextLine(); var line = TextLine.Pool.Get();
line.TextBlock = this; line.TextBlock = this;
line.YCoord = _measuredHeight; line.YCoord = _measuredHeight;

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

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Topten.RichTextKit.Utils;
namespace Topten.RichTextKit namespace Topten.RichTextKit
{ {
@ -296,5 +297,15 @@ namespace Topten.RichTextKit
/// Internal List of runs /// Internal List of runs
/// </summary> /// </summary>
internal List<FontRun> RunsInternal = new List<FontRun>(); 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); return new ArraySliceEnumerator<T>(_data, 0, _length);
} }
/*
public T[] SetMapping(Slice<T> data, Slice<int> mapping) public T[] SetMapping(Slice<T> data, Slice<int> mapping)
{ {
Length = mapping.Length; Length = mapping.Length;
@ -221,5 +222,6 @@ namespace Topten.RichTextKit.Utils
} }
return _data; 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(); bt.git_tag();
// Push nuget package // 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");
} }