Skip to content

Commit 6ccf624

Browse files
committed
refactored
1 parent 9f4b278 commit 6ccf624

16 files changed

Lines changed: 120 additions & 129 deletions

File tree

benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,26 +94,35 @@ public void PolylineDecoder_Decode_Memory() {
9494
}
9595

9696
private sealed class StringPolylineDecoder : AbstractPolylineDecoder<string, (double Latitude, double Longitude)> {
97+
private int _latitudeState;
98+
private int _longitudeState;
99+
97100
protected override (double Latitude, double Longitude) Read(PolylineReader reader) =>
98-
(reader.Read(), reader.Read());
101+
(reader.Read(ref _latitudeState), reader.Read(ref _longitudeState));
99102

100103
protected override ReadOnlyMemory<char> GetReadOnlyMemory(in string polyline) {
101104
return polyline?.AsMemory() ?? Memory<char>.Empty;
102105
}
103106
}
104107

105108
private sealed class CharArrayPolylineDecoder : AbstractPolylineDecoder<char[], (double Latitude, double Longitude)> {
109+
private int _latitudeState;
110+
private int _longitudeState;
111+
106112
protected override (double Latitude, double Longitude) Read(PolylineReader reader) =>
107-
(reader.Read(), reader.Read());
113+
(reader.Read(ref _latitudeState), reader.Read(ref _longitudeState));
108114

109115
protected override ReadOnlyMemory<char> GetReadOnlyMemory(in char[] polyline) {
110116
return polyline?.AsMemory() ?? Memory<char>.Empty;
111117
}
112118
}
113119

114120
private sealed class MemoryCharPolylineDecoder : AbstractPolylineDecoder<ReadOnlyMemory<char>, (double Latitude, double Longitude)> {
121+
private int _latitudeState;
122+
private int _longitudeState;
123+
115124
protected override (double Latitude, double Longitude) Read(PolylineReader reader) =>
116-
(reader.Read(), reader.Read());
125+
(reader.Read(ref _latitudeState), reader.Read(ref _longitudeState));
117126

118127
protected override ReadOnlyMemory<char> GetReadOnlyMemory(in ReadOnlyMemory<char> polyline) {
119128
return polyline;

benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,13 @@ public void PolylineEncoder_Encode_List() {
8686
}
8787

8888
private sealed class StringPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> {
89+
private int _latitudeState;
90+
private int _longitudeState;
91+
8992
protected override string CreatePolyline(ReadOnlySpan<char> polyline) => polyline.ToString();
9093
protected override void Write((double Latitude, double Longitude) item, ref PolylineWriter writer) {
91-
writer.Write(item.Latitude);
92-
writer.Write(item.Longitude);
94+
writer.Write(item.Latitude, ref _latitudeState);
95+
writer.Write(item.Longitude, ref _longitudeState);
9396
}
9497
}
9598
}

samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineDecoder.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ namespace PolylineAlgorithm.NetTopologySuite.Sample;
1414
/// Polyline decoder using NetTopologySuite.
1515
/// </summary>
1616
internal sealed class NetTopologyPolylineDecoder : AbstractPolylineDecoder<string, Point> {
17+
private int _latitudeState;
18+
private int _longitudeState;
19+
1720
/// <summary>
1821
/// Converts polyline string to read-only memory.
1922
/// </summary>
@@ -29,8 +32,8 @@ protected override ReadOnlyMemory<char> GetReadOnlyMemory(in string polyline) {
2932
/// <param name="reader">The reader provided by the engine. Field 0 = latitude, field 1 = longitude.</param>
3033
/// <returns>Point instance.</returns>
3134
protected override Point Read(PolylineReader reader) {
32-
double latitude = reader.Read();
33-
double longitude = reader.Read();
35+
double latitude = reader.Read(ref _latitudeState);
36+
double longitude = reader.Read(ref _longitudeState);
3437

3538
// NetTopologySuite Point: x = longitude, y = latitude
3639
return new Point(longitude, latitude);

samples/PolylineAlgorithm.NetTopologySuite.Sample/NetTopologyPolylineEncoder.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ namespace PolylineAlgorithm.NetTopologySuite.Sample;
1414
/// Polyline encoder using NetTopologySuite's Point type.
1515
/// </summary>
1616
internal sealed class NetTopologyPolylineEncoder : AbstractPolylineEncoder<Point, string> {
17+
private int _latitudeState;
18+
private int _longitudeState;
19+
1720
/// <summary>
1821
/// Creates encoded polyline string from span.
1922
/// </summary>
@@ -36,7 +39,7 @@ protected override void Write(Point item, ref PolylineWriter writer) {
3639
ArgumentNullException.ThrowIfNull(item);
3740

3841
// NetTopologySuite Point: Y = latitude, X = longitude
39-
writer.Write(item.Y);
40-
writer.Write(item.X);
42+
writer.Write(item.Y, ref _latitudeState);
43+
writer.Write(item.X, ref _longitudeState);
4144
}
4245
}

src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//
1+
//
22
// Copyright © Pete Sramek. All rights reserved.
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
@@ -96,10 +96,9 @@ public IEnumerable<TCoordinate> Decode(TPolyline polyline, CancellationToken can
9696
while (!reader.IsEmpty) {
9797
cancellationToken.ThrowIfCancellationRequested();
9898

99-
reader.BeginItem();
10099
TCoordinate item = Read(reader);
101100

102-
_logger?.LogDecodedItemDebug(reader.SlotIndex, reader.Position);
101+
//_logger?.LogDecodedItemDebug(reader.SlotIndex, reader.Position);
103102

104103
yield return item;
105104
}

src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//
1+
//
22
// Copyright © Pete Sramek. All rights reserved.
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
@@ -105,7 +105,6 @@ public TPolyline Encode(ReadOnlySpan<TCoordinate> coordinates, CancellationToken
105105
for (var i = 0; i < coordinates.Length; i++) {
106106
cancellationToken.ThrowIfCancellationRequested();
107107

108-
writer.BeginItem();
109108
Write(coordinates[i], ref writer);
110109
}
111110

src/PolylineAlgorithm/Internal/PolylineReader.cs

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//
1+
//
22
// Copyright © Pete Sramek. All rights reserved.
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
@@ -20,15 +20,11 @@ namespace PolylineAlgorithm.Internal;
2020
public sealed class PolylineReader {
2121
private readonly ReadOnlyMemory<char> _sequence;
2222
private int _position;
23-
private int[] _accumulated;
24-
private int _slotIndex;
2523
private readonly uint _precision;
2624

2725
internal PolylineReader(ReadOnlyMemory<char> sequence, uint precision) {
2826
_sequence = sequence;
2927
_precision = precision;
30-
_accumulated = [];
31-
_slotIndex = 0;
3228
_position = 0;
3329
}
3430

@@ -38,33 +34,16 @@ internal PolylineReader(ReadOnlyMemory<char> sequence, uint precision) {
3834
/// <exception cref="InvalidPolylineException">
3935
/// Thrown when the data runs out before the formatter has finished reading an item's fields.
4036
/// </exception>
41-
public double Read() {
42-
// Grow the per-slot accumulator array on the first item (field discovery).
43-
if (_slotIndex >= _accumulated.Length) {
44-
Array.Resize(ref _accumulated, _slotIndex + 1);
45-
}
46-
47-
if (!PolylineEncoding.TryReadValue(ref _accumulated[_slotIndex], _sequence, ref _position)) {
37+
public double Read(ref int state) {
38+
if (!PolylineEncoding.TryReadValue(ref state, _sequence, ref _position)) {
4839
ExceptionGuard.ThrowInvalidPolylineFormat(_position);
4940
}
5041

51-
double result = PolylineEncoding.Denormalize(_accumulated[_slotIndex], _precision);
52-
_slotIndex++;
42+
double result = PolylineEncoding.Denormalize(state, _precision);
43+
5344
return result;
5445
}
5546

56-
/// <summary>
57-
/// Resets the intra-item slot index so that delta accumulation is applied to the correct field slot
58-
/// on the next item. Must be called by the engine before each call to the formatter's Read method.
59-
/// </summary>
60-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61-
internal void BeginItem() => _slotIndex = 0;
62-
63-
/// <summary>
64-
/// Gets the number of fields read in the current (or most recently completed) item.
65-
/// </summary>
66-
internal int SlotIndex => _slotIndex;
67-
6847
/// <summary>
6948
/// Gets the current character position in the encoded sequence.
7049
/// </summary>

src/PolylineAlgorithm/Internal/PolylineWriter.cs

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//
1+
//
22
// Copyright © Pete Sramek. All rights reserved.
33
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
44
//
@@ -23,48 +23,28 @@ public ref struct PolylineWriter {
2323
private Span<char> _buffer;
2424
private int _position;
2525
private readonly uint _precision;
26-
private int[] _previous;
27-
private int _slotIndex;
2826

2927
internal PolylineWriter(Span<char> buffer, uint precision) {
3028
_buffer = buffer;
3129
_precision = precision;
32-
_previous = [];
33-
_slotIndex = 0;
3430
_position = 0;
3531
}
3632

3733
/// <summary>
3834
/// Emits one field value into the encoding pipeline.
3935
/// </summary>
40-
public void Write(double value) {
41-
// Grow the per-slot delta array on the first item (field discovery).
42-
if (_slotIndex >= _previous.Length) {
43-
Array.Resize(ref _previous, _slotIndex + 1);
44-
}
45-
36+
public void Write(double value, ref int state) {
4637
int normalized = PolylineEncoding.Normalize(value, _precision);
47-
int delta = normalized - _previous[_slotIndex];
48-
_previous[_slotIndex] = normalized;
49-
_slotIndex++;
38+
39+
int delta = normalized - state;
40+
41+
state = normalized;
5042

5143
if (!PolylineEncoding.TryWriteValue(delta, _buffer, ref _position)) {
5244
ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer();
5345
}
5446
}
5547

56-
/// <summary>
57-
/// Resets the intra-item slot index so delta state is applied to the correct field on the next item.
58-
/// Must be called by the engine before each call to the formatter's Write method.
59-
/// </summary>
60-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61-
internal void BeginItem() => _slotIndex = 0;
62-
63-
/// <summary>
64-
/// Gets the number of fields written in the current (or most recently completed) item.
65-
/// </summary>
66-
internal int SlotIndex => _slotIndex;
67-
6848
/// <summary>
6949
/// Returns the encoded polyline characters written so far.
7050
/// </summary>

src/PolylineAlgorithm/PolylineAlgorithm.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.1</TargetFramework>
5+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
56
</PropertyGroup>
67

78
<PropertyGroup>

src/PolylineAlgorithm/PolylineEncoding.cs

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace PolylineAlgorithm;
1010

1111
using System;
1212
using System.Numerics;
13+
using System.Runtime.CompilerServices;
1314
using System.Runtime.InteropServices;
1415

1516
/// <summary>
@@ -389,35 +390,43 @@ public static void ValidateFormat(ReadOnlySpan<char> polyline) {
389390
/// <exception cref="ArgumentException">
390391
/// Thrown when an invalid character is found in the polyline segment.
391392
/// </exception>
392-
public static void ValidateCharRange(ReadOnlySpan<char> polyline) {
393+
public static unsafe void ValidateCharRange(ReadOnlySpan<char> polyline) {
393394
int length = polyline.Length;
394-
int vectorSize = Vector<ushort>.Count;
395-
396-
int i = 0;
397-
for (; i <= length - vectorSize; i += vectorSize) {
398-
var span = MemoryMarshal.Cast<char, ushort>(polyline.Slice(i, vectorSize));
399-
#if NET5_0_OR_GREATER
400-
var chars = new Vector<ushort>(span);
401-
#else
402-
var chars = new Vector<ushort>(span.ToArray());
403-
#endif
404-
var belowMin = Vector.LessThan(chars, MinVector);
405-
var aboveMax = Vector.GreaterThan(chars, MaxVector);
406-
if (Vector.BitwiseOr(belowMin, aboveMax) != Vector<ushort>.Zero) {
407-
// Fallback to scalar check for this block
408-
for (int j = 0; j < vectorSize; j++) {
409-
char character = polyline[i + j];
410-
if (character < Min || character > Max) {
411-
ExceptionGuard.ThrowInvalidPolylineCharacter(character, i + j);
395+
int vecSize = Vector<ushort>.Count;
396+
397+
fixed (char* p = polyline) {
398+
ushort* up = (ushort*)p;
399+
400+
int i = 0;
401+
402+
// SIMD loop
403+
for (; i <= length - vecSize; i += vecSize) {
404+
// Load vector directly without allocations
405+
var vec = Unsafe.Read<Vector<ushort>>(up + i);
406+
407+
var belowMin = Vector.LessThan(vec, MinVector);
408+
var aboveMax = Vector.GreaterThan(vec, MaxVector);
409+
410+
var invalid = Vector.BitwiseOr(belowMin, aboveMax);
411+
412+
if (!Vector.EqualsAll(invalid, Vector<ushort>.Zero)) {
413+
// Fallback to scalar for this block
414+
for (int j = 0; j < vecSize; j++) {
415+
ushort ch = up[i + j];
416+
if (ch < Min || ch > Max) {
417+
ExceptionGuard.ThrowInvalidPolylineCharacter((char)ch, i + j);
418+
}
412419
}
413420
}
414421
}
415-
}
416422

417-
for (; i < length; i++) {
418-
char character = polyline[i];
419-
if (character < Min || character > Max) {
420-
ExceptionGuard.ThrowInvalidPolylineCharacter(character, i);
423+
// Tail
424+
for (; i < length; i++) {
425+
ushort ch = up[i];
426+
427+
if (ch < Min || ch > Max) {
428+
ExceptionGuard.ThrowInvalidPolylineCharacter((char)ch, i);
429+
}
421430
}
422431
}
423432
}
@@ -436,24 +445,32 @@ public static void ValidateCharRange(ReadOnlySpan<char> polyline) {
436445
/// Thrown when a block exceeds 7 characters or the polyline does not end with a valid block terminator.
437446
/// </exception>
438447
public static void ValidateBlockLength(ReadOnlySpan<char> polyline) {
448+
const int MaxBlockLen = Defaults.Polyline.Block.Length.Max;
449+
439450
int blockLen = 0;
440-
bool foundBlockEnd = false;
451+
bool foundEnd = false;
441452

442453
for (int i = 0; i < polyline.Length; i++) {
443454
blockLen++;
444455

445-
if (polyline[i] < End) {
446-
foundBlockEnd = true;
447-
if (blockLen > Defaults.Polyline.Block.Length.Max) {
456+
if (polyline[i] >= End) {
457+
if (blockLen > MaxBlockLen) {
448458
ExceptionGuard.ThrowPolylineBlockTooLong(i - blockLen + 1);
449459
}
450-
blockLen = 0;
451-
} else {
452-
foundBlockEnd = false;
460+
461+
foundEnd = false;
462+
continue;
453463
}
464+
465+
if (blockLen > MaxBlockLen) {
466+
ExceptionGuard.ThrowPolylineBlockTooLong(i - blockLen + 1);
467+
}
468+
469+
blockLen = 0;
470+
foundEnd = true;
454471
}
455472

456-
if (!foundBlockEnd) {
473+
if (!foundEnd) {
457474
ExceptionGuard.ThrowInvalidPolylineBlockTerminator();
458475
}
459476
}

0 commit comments

Comments
 (0)