From 85159c069c08df23983fa0163c78bf851f46a515 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 20 Jun 2018 18:52:04 -0500 Subject: [PATCH] Span-ification v2. Remove CommandToken and just operate directly on the single span. Memory allocation is now down to less than 5KB. --- .../Media/PathMarkupParser.cs | 534 ++++++++---------- 1 file changed, 238 insertions(+), 296 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs index 8f1d8ebc71..384c96a7d6 100644 --- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs +++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs @@ -105,61 +105,64 @@ namespace Avalonia.Media while(!span.IsEmpty) { - var commandToken = CommandToken.Parse(ref span); - try + if(!ReadCommand(ref span, out var command, out var relative)) { - do - { - switch (commandToken.Command) - { - case Command.None: - break; - case Command.FillRule: - SetFillRule(commandToken); - break; - case Command.Move: - AddMove(commandToken); - break; - case Command.Line: - AddLine(commandToken); - break; - case Command.HorizontalLine: - AddHorizontalLine(commandToken); - break; - case Command.VerticalLine: - AddVerticalLine(commandToken); - break; - case Command.CubicBezierCurve: - AddCubicBezierCurve(commandToken); - break; - case Command.QuadraticBezierCurve: - AddQuadraticBezierCurve(commandToken); - break; - case Command.SmoothCubicBezierCurve: - AddSmoothCubicBezierCurve(commandToken); - break; - case Command.SmoothQuadraticBezierCurve: - AddSmoothQuadraticBezierCurve(commandToken); - break; - case Command.Arc: - AddArc(commandToken); - break; - case Command.Close: - CloseFigure(); - break; - default: - throw new NotSupportedException("Unsupported command"); - } - } while (commandToken.HasImplicitCommands); - } - catch (InvalidDataException) - { - break; + return; } - catch (NotSupportedException) + + bool initialCommand = true; + + do { - break; - } + if (!initialCommand) + { + span = ReadSeparator(span); + } + + switch (command) + { + case Command.None: + break; + case Command.FillRule: + SetFillRule(ref span); + break; + case Command.Move: + AddMove(ref span, relative); + break; + case Command.Line: + AddLine(ref span, relative); + break; + case Command.HorizontalLine: + AddHorizontalLine(ref span, relative); + break; + case Command.VerticalLine: + AddVerticalLine(ref span, relative); + break; + case Command.CubicBezierCurve: + AddCubicBezierCurve(ref span, relative); + break; + case Command.QuadraticBezierCurve: + AddQuadraticBezierCurve(ref span, relative); + break; + case Command.SmoothCubicBezierCurve: + AddSmoothCubicBezierCurve(ref span, relative); + break; + case Command.SmoothQuadraticBezierCurve: + AddSmoothQuadraticBezierCurve(ref span, relative); + break; + case Command.Arc: + AddArc(ref span, relative); + break; + case Command.Close: + CloseFigure(); + break; + default: + throw new NotSupportedException("Unsupported command"); + } + + initialCommand = false; + } while (PeekArgument(span)); + } if (_isOpen) @@ -180,11 +183,28 @@ namespace Avalonia.Media _isOpen = true; } - private void SetFillRule(CommandToken commandToken) + private void SetFillRule(ref ReadOnlySpan span) { - var fillRule = commandToken.ReadFillRule(); + if (!ReadArgument(ref span, out var fillRule) || fillRule.Length != 1) + { + throw new InvalidDataException("Invalid fill rule."); + } + + FillRule rule; - _geometryContext.SetFillRule(fillRule); + switch (fillRule[0]) + { + case '0': + rule = FillRule.EvenOdd; + break; + case '1': + rule = FillRule.NonZero; + break; + default: + throw new InvalidDataException("Invalid fill rule"); + } + + _geometryContext.SetFillRule(rule); } private void CloseFigure() @@ -199,21 +219,22 @@ namespace Avalonia.Media _isOpen = false; } - private void AddMove(CommandToken commandToken) + private void AddMove(ref ReadOnlySpan span, bool relative) { - var currentPoint = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + var currentPoint = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); _currentPoint = currentPoint; CreateFigure(); - while (commandToken.HasImplicitCommands) + while (PeekArgument(span)) { - AddLine(commandToken); + span = ReadSeparator(span); + AddLine(ref span, relative); - if (!commandToken.IsRelative) + if (!relative) { _currentPoint = currentPoint; CreateFigure(); @@ -221,11 +242,11 @@ namespace Avalonia.Media } } - private void AddLine(CommandToken commandToken) + private void AddLine(ref ReadOnlySpan span, bool relative) { - _currentPoint = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + _currentPoint = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); if (!_isOpen) { @@ -235,11 +256,11 @@ namespace Avalonia.Media _geometryContext.LineTo(_currentPoint); } - private void AddHorizontalLine(CommandToken commandToken) + private void AddHorizontalLine(ref ReadOnlySpan span, bool relative) { - _currentPoint = commandToken.IsRelative - ? new Point(_currentPoint.X + commandToken.ReadDouble(), _currentPoint.Y) - : _currentPoint.WithX(commandToken.ReadDouble()); + _currentPoint = relative + ? new Point(_currentPoint.X + ReadDouble(ref span), _currentPoint.Y) + : _currentPoint.WithX(ReadDouble(ref span)); if (!_isOpen) { @@ -249,11 +270,11 @@ namespace Avalonia.Media _geometryContext.LineTo(_currentPoint); } - private void AddVerticalLine(CommandToken commandToken) + private void AddVerticalLine(ref ReadOnlySpan span, bool relative) { - _currentPoint = commandToken.IsRelative - ? new Point(_currentPoint.X, _currentPoint.Y + commandToken.ReadDouble()) - : _currentPoint.WithY(commandToken.ReadDouble()); + _currentPoint = relative + ? new Point(_currentPoint.X, _currentPoint.Y + ReadDouble(ref span)) + : _currentPoint.WithY(ReadDouble(ref span)); if (!_isOpen) { @@ -263,21 +284,25 @@ namespace Avalonia.Media _geometryContext.LineTo(_currentPoint); } - private void AddCubicBezierCurve(CommandToken commandToken) + private void AddCubicBezierCurve(ref ReadOnlySpan span, bool relative) { - var point1 = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + var point1 = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); - var point2 = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + span = ReadSeparator(span); + + var point2 = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); _previousControlPoint = point2; - var point3 = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + span = ReadSeparator(span); + + var point3 = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); if (!_isOpen) { @@ -289,17 +314,19 @@ namespace Avalonia.Media _currentPoint = point3; } - private void AddQuadraticBezierCurve(CommandToken commandToken) + private void AddQuadraticBezierCurve(ref ReadOnlySpan span, bool relative) { - var start = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + var start = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); _previousControlPoint = start; - var end = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + span = ReadSeparator(span); + + var end = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); if (!_isOpen) { @@ -311,15 +338,17 @@ namespace Avalonia.Media _currentPoint = end; } - private void AddSmoothCubicBezierCurve(CommandToken commandToken) + private void AddSmoothCubicBezierCurve(ref ReadOnlySpan span, bool relative) { - var point2 = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + var point2 = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); + + span = ReadSeparator(span); - var end = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + var end = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); if (_previousControlPoint != null) { @@ -338,11 +367,11 @@ namespace Avalonia.Media _currentPoint = end; } - private void AddSmoothQuadraticBezierCurve(CommandToken commandToken) + private void AddSmoothQuadraticBezierCurve(ref ReadOnlySpan span, bool relative) { - var end = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + var end = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); if (_previousControlPoint != null) { @@ -359,19 +388,25 @@ namespace Avalonia.Media _currentPoint = end; } - private void AddArc(CommandToken commandToken) + private void AddArc(ref ReadOnlySpan span, bool relative) { - var size = commandToken.ReadSize(); + var size = ReadSize(ref span); - var rotationAngle = commandToken.ReadDouble(); + span = ReadSeparator(span); - var isLargeArc = commandToken.ReadBool(); + var rotationAngle = ReadDouble(ref span); + span = ReadSeparator(span); + var isLargeArc = ReadBool(ref span); - var sweepDirection = commandToken.ReadBool() ? SweepDirection.Clockwise : SweepDirection.CounterClockwise; + span = ReadSeparator(span); + + var sweepDirection = ReadBool(ref span) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise; + + span = ReadSeparator(span); - var end = commandToken.IsRelative - ? commandToken.ReadRelativePoint(_currentPoint) - : commandToken.ReadPoint(); + var end = relative + ? ReadRelativePoint(ref span, _currentPoint) + : ReadPoint(ref span); if (!_isOpen) { @@ -385,227 +420,134 @@ namespace Avalonia.Media _previousControlPoint = null; } - private class CommandToken + private static bool PeekArgument(ReadOnlySpan span) { - private CommandToken(Command command, bool isRelative, List arguments) - { - Command = command; - - IsRelative = isRelative; - - Arguments = arguments; - } - - public Command Command { get; } + span = SkipWhitespace(span); - public bool IsRelative { get; } + return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || char.IsDigit(span[0])); + } - public bool HasImplicitCommands + private static bool ReadArgument(ref ReadOnlySpan remaining, out ReadOnlySpan argument) + { + remaining = SkipWhitespace(remaining); + if (remaining.IsEmpty) { - get - { - if (CurrentPosition == 0 && Arguments.Count > 0) - { - return true; - } - - return CurrentPosition < Arguments.Count - 1; - } + argument = ReadOnlySpan.Empty; + return false; } - private int CurrentPosition { get; set; } - - private List Arguments { get; } - - public static CommandToken Parse(ref ReadOnlySpan span) + var valid = false; + int i = 0; + if (remaining[i] == '-') { - if (!ReadCommand(ref span, out var command, out var isRelative)) - { - throw new InvalidDataException("No path command declared."); - } - - span = span.Slice(1); - span = SkipWhitespace(span); - - var arguments = new List(); - - while (ReadArgument(ref span, out var argument)) - { - arguments.Add(argument.ToString()); - span = ReadSeparator(span); - } - - return new CommandToken(command, isRelative, arguments); + i++; } + for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true; - public FillRule ReadFillRule() + if (i < remaining.Length && remaining[i] == '.') { - if (CurrentPosition == Arguments.Count) - { - throw new InvalidDataException("Invalid fill rule"); - } - - var value = Arguments[CurrentPosition]; - - CurrentPosition++; - - switch (value) - { - case "0": - { - return FillRule.EvenOdd; - } - - case "1": - { - return FillRule.NonZero; - } - - default: - throw new InvalidDataException("Invalid fill rule"); - } + valid = false; + i++; } + for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true; - public bool ReadBool() + if (!valid) { - if (CurrentPosition == Arguments.Count) - { - throw new InvalidDataException("Invalid boolean value"); - } - - var value = Arguments[CurrentPosition]; - - CurrentPosition++; - - switch (value) - { - case "1": - { - return true; - } - - case "0": - { - return false; - } - - default: - throw new InvalidDataException("Invalid boolean value"); - } + argument = ReadOnlySpan.Empty; + return false; } + argument = remaining.Slice(0, i); + remaining = remaining.Slice(i); + return true; + } - public double ReadDouble() - { - if (CurrentPosition == Arguments.Count) - { - throw new InvalidDataException("Invalid double value"); - } - - var value = Arguments[CurrentPosition]; - - CurrentPosition++; - - return double.Parse(value, CultureInfo.InvariantCulture); - } - public Size ReadSize() + private static ReadOnlySpan ReadSeparator(ReadOnlySpan span) + { + span = SkipWhitespace(span); + if (!span.IsEmpty && span[0] == ',') { - var width = ReadDouble(); - - var height = ReadDouble(); - - return new Size(width, height); + span = span.Slice(1); } + return span; + } - public Point ReadPoint() - { - var x = ReadDouble(); - - var y = ReadDouble(); - - return new Point(x, y); - } + private static ReadOnlySpan SkipWhitespace(ReadOnlySpan span) + { + int i = 0; + for (; i < span.Length && char.IsWhiteSpace(span[i]); i++) ; + return span.Slice(i); + } - public Point ReadRelativePoint(Point origin) + private bool ReadBool(ref ReadOnlySpan span) + { + if (!ReadArgument(ref span, out var boolValue) || boolValue.Length != 1) { - var x = ReadDouble(); - - var y = ReadDouble(); - - return new Point(origin.X + x, origin.Y + y); + throw new InvalidDataException("Invalid bool rule."); } - - private static bool ReadCommand(ref ReadOnlySpan span, out Command command, out bool relative) + + switch (boolValue[0]) { - span = SkipWhitespace(span); - if (span.IsEmpty) - { - command = default; - relative = false; + case '0': return false; - } - - var c = span[0]; - - if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out command)) - { - throw new InvalidDataException("Unexpected path command '" + c + "'."); - } - - relative = char.IsLower(c); - - return true; + case '1': + return true; + default: + throw new InvalidDataException("Invalid bool rule"); } + } - private static bool ReadArgument(ref ReadOnlySpan remaining, out ReadOnlySpan argument) + private double ReadDouble(ref ReadOnlySpan span) + { + if (!ReadArgument(ref span, out var doubleValue)) { - if (remaining.IsEmpty) - { - argument = ReadOnlySpan.Empty; - return false; - } + throw new InvalidDataException("Invalid double value"); + } - var valid = false; - int i = 0; - if (remaining[i] == '-') - { - i++; - } - for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true ; + return double.Parse(doubleValue.ToString(), CultureInfo.InvariantCulture); + } - if (i < remaining.Length && remaining[i] == '.') - { - valid = false; - i++; - } - for (; i < remaining.Length && char.IsNumber(remaining[i]); i++) valid = true ; + private Size ReadSize(ref ReadOnlySpan span) + { + var width = ReadDouble(ref span); + span = ReadSeparator(span); + var height = ReadDouble(ref span); + return new Size(width, height); + } - if (!valid) - { - argument = ReadOnlySpan.Empty; - return false; - } - argument = remaining.Slice(0, i); - remaining = remaining.Slice(i); - return true; - } + private Point ReadPoint(ref ReadOnlySpan span) + { + var x = ReadDouble(ref span); + span = ReadSeparator(span); + var y = ReadDouble(ref span); + return new Point(x, y); + } - private static ReadOnlySpan ReadSeparator(ReadOnlySpan span) + private Point ReadRelativePoint(ref ReadOnlySpan span, Point origin) + { + var x = ReadDouble(ref span); + span = ReadSeparator(span); + var y = ReadDouble(ref span); + return new Point(origin.X + x, origin.Y + y); + } + + private bool ReadCommand(ref ReadOnlySpan span, out Command command, out bool relative) + { + span = SkipWhitespace(span); + if (span.IsEmpty) { - span = SkipWhitespace(span); - if (!span.IsEmpty && span[0] == ',') - { - span = span.Slice(1); - } - return SkipWhitespace(span); + command = default; + relative = false; + return false; } - - private static ReadOnlySpan SkipWhitespace(ReadOnlySpan span) + var c = span[0]; + if (!s_commands.TryGetValue(char.ToUpperInvariant(c), out command)) { - int i = 0; - for (; i < span.Length && char.IsWhiteSpace(span[i]); i++) ; - return span.Slice(i); + throw new InvalidDataException("Unexpected path command '" + c + "'."); } + relative = char.IsLower(c); + span = span.Slice(1); + return true; } } } \ No newline at end of file