@@ -7,12 +7,10 @@ namespace PolylineAlgorithm;
77
88using Microsoft . Extensions . Logging ;
99using PolylineAlgorithm . Abstraction ;
10- using PolylineAlgorithm . Internal ;
1110using PolylineAlgorithm . Internal . Diagnostics ;
1211using System ;
1312using System . Buffers ;
14- using System . Diagnostics ;
15- using System . Runtime . CompilerServices ;
13+ using System . Collections . Generic ;
1614using System . Threading ;
1715
1816/// <summary>
@@ -60,73 +58,24 @@ public PolylineEncoder(PolylineOptions<TValue, TPolyline> options) {
6058 /// <returns>
6159 /// An instance of <typeparamref name="TPolyline"/> representing the encoded values.
6260 /// </returns>
61+ /// <exception cref="ArgumentNullException">
62+ /// Thrown when <paramref name="coordinates"/> is <see langword="null"/>.
63+ /// </exception>
6364 /// <exception cref="ArgumentException">
6465 /// Thrown when <paramref name="coordinates"/> is empty.
6566 /// </exception>
66- /// <exception cref="InvalidOperationException">
67- /// Thrown when the internal encoding buffer cannot accommodate the encoded value.
68- /// </exception>
6967 /// <exception cref="OperationCanceledException">
7068 /// Thrown when <paramref name="cancellationToken"/> is canceled.
7169 /// </exception>
72- public TPolyline Encode ( ReadOnlySpan < TValue > coordinates , CancellationToken cancellationToken = default ) {
73- const string OperationName = nameof ( Encode ) ;
74-
75- _logger . LogOperationStartedDebug ( OperationName ) ;
76-
77- Debug . Assert ( coordinates . Length >= 0 , "Count must be non-negative." ) ;
70+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Design" , "CA1062:Validate arguments of public methods" , Justification = "Null is verified before use via ExceptionGuard.ThrowArgumentNull, which is annotated [DoesNotReturn]. CA1062 does not recognise custom [DoesNotReturn] helpers as null guards." ) ]
71+ public TPolyline Encode ( IEnumerable < TValue > coordinates , CancellationToken cancellationToken = default ) {
72+ _logger . LogOperationStartedDebug ( nameof ( Encode ) ) ;
7873
79- if ( coordinates . Length < 1 ) {
80- _logger . LogOperationFailedDebug ( OperationName ) ;
81- _logger . LogEmptyArgumentWarning ( nameof ( coordinates ) ) ;
82- ExceptionGuard . ThrowArgumentCannotBeEmptyEnumerationMessage ( nameof ( coordinates ) ) ;
74+ if ( coordinates is null ) {
75+ ExceptionGuard . ThrowArgumentNull ( nameof ( coordinates ) ) ;
8376 }
8477
85- int width = _formatter . Width ;
86- int length = GetMaxBufferLength ( coordinates . Length , width ) ;
87-
88- char [ ] ? temp = length <= _options . StackAllocLimit
89- ? null
90- : ArrayPool < char > . Shared . Rent ( length ) ;
91-
92- Span < char > buffer = temp is null ? stackalloc char [ length ] : temp . AsSpan ( 0 , length ) ;
93-
94- int position = 0 ;
95- long [ ] previous = new long [ width ] ;
96- long [ ] values = new long [ width ] ;
97-
98- SeedPrevious ( previous , null ) ;
99-
100- try {
101- for ( int i = 0 ; i < coordinates . Length ; i ++ ) {
102- cancellationToken . ThrowIfCancellationRequested ( ) ;
103-
104- _formatter . GetValues ( coordinates [ i ] , values . AsSpan ( ) ) ;
105-
106- for ( int j = 0 ; j < width ; j ++ ) {
107- long current = values [ j ] ;
108- long delta = current - previous [ j ] ;
109- previous [ j ] = current ;
110-
111- if ( ! PolylineEncoding . TryWriteValue ( delta , buffer , ref position ) ) {
112- _logger . LogOperationFailedDebug ( OperationName ) ;
113- _logger . LogCannotWriteValueToBufferWarning ( position , i ) ;
114- ExceptionGuard . ThrowCouldNotWriteEncodedValueToBuffer ( ) ;
115- }
116- }
117- }
118-
119- // Convert to string inside the try block so the buffer is still valid.
120- string encodedResult = buffer [ ..position ] . ToString ( ) ;
121-
122- _logger . LogOperationFinishedDebug ( OperationName ) ;
123-
124- return _formatter . Write ( encodedResult . AsMemory ( ) ) ;
125- } finally {
126- if ( temp is not null ) {
127- ArrayPool < char > . Shared . Return ( temp ) ;
128- }
129- }
78+ return EncodeCore ( coordinates , null , cancellationToken ) ;
13079 }
13180
13281 /// <summary>
@@ -140,101 +89,101 @@ public TPolyline Encode(ReadOnlySpan<TValue> coordinates, CancellationToken canc
14089 /// Per-call options that control the starting delta baseline. Pass <see langword="null"/> or an
14190 /// instance with <see cref="PolylineEncodingOptions{TValue}.Previous"/> set to
14291 /// <see langword="null"/> to use the formatter's default baseline (same as calling
143- /// <see cref="Encode(ReadOnlySpan {TValue}, CancellationToken)"/>).
92+ /// <see cref="Encode(IEnumerable {TValue}, CancellationToken)"/>).
14493 /// </param>
14594 /// <param name="cancellationToken">A token that can be used to cancel the operation.</param>
14695 /// <returns>
14796 /// An instance of <typeparamref name="TPolyline"/> representing the encoded values.
14897 /// </returns>
98+ /// <exception cref="ArgumentNullException">
99+ /// Thrown when <paramref name="coordinates"/> is <see langword="null"/>.
100+ /// </exception>
149101 /// <exception cref="ArgumentException">
150102 /// Thrown when <paramref name="coordinates"/> is empty.
151103 /// </exception>
152- /// <exception cref="InvalidOperationException">
153- /// Thrown when the internal encoding buffer cannot accommodate the encoded value.
154- /// </exception>
155104 /// <exception cref="OperationCanceledException">
156105 /// Thrown when <paramref name="cancellationToken"/> is canceled.
157106 /// </exception>
158- public TPolyline Encode ( ReadOnlySpan < TValue > coordinates , PolylineEncodingOptions < TValue > ? options , CancellationToken cancellationToken ) {
159- const string OperationName = nameof ( Encode ) ;
107+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Design" , "CA1062:Validate arguments of public methods" , Justification = "Null is verified before use via ExceptionGuard.ThrowArgumentNull, which is annotated [DoesNotReturn]. CA1062 does not recognise custom [DoesNotReturn] helpers as null guards." ) ]
108+ public TPolyline Encode ( IEnumerable < TValue > coordinates , PolylineEncodingOptions < TValue > ? options , CancellationToken cancellationToken ) {
109+ _logger . LogOperationStartedDebug ( nameof ( Encode ) ) ;
160110
161- _logger . LogOperationStartedDebug ( OperationName ) ;
111+ if ( coordinates is null ) {
112+ ExceptionGuard . ThrowArgumentNull ( nameof ( coordinates ) ) ;
113+ }
162114
163- Debug . Assert ( coordinates . Length >= 0 , "Count must be non-negative." ) ;
115+ return EncodeCore ( coordinates , options , cancellationToken ) ;
116+ }
164117
165- if ( coordinates . Length < 1 ) {
166- _logger . LogOperationFailedDebug ( OperationName ) ;
167- _logger . LogEmptyArgumentWarning ( nameof ( coordinates ) ) ;
168- ExceptionGuard . ThrowArgumentCannotBeEmptyEnumerationMessage ( nameof ( coordinates ) ) ;
169- }
118+ private TPolyline EncodeCore ( IEnumerable < TValue > coordinates , PolylineEncodingOptions < TValue > ? options , CancellationToken cancellationToken ) {
119+ const string OperationName = nameof ( Encode ) ;
120+ const int MaxStackWidth = 8 ;
170121
171122 int width = _formatter . Width ;
172- int length = GetMaxBufferLength ( coordinates . Length , width ) ;
173123
174- char [ ] ? temp = length <= _options . StackAllocLimit
175- ? null
176- : ArrayPool < char > . Shared . Rent ( length ) ;
124+ Span < long > previous = width <= MaxStackWidth ? stackalloc long [ MaxStackWidth ] : new long [ width ] ;
125+ Span < long > values = width <= MaxStackWidth ? stackalloc long [ MaxStackWidth ] : new long [ width ] ;
126+ previous = previous [ ..width ] ;
127+ values = values [ ..width ] ;
177128
178- Span < char > buffer = temp is null ? stackalloc char [ length ] : temp . AsSpan ( 0 , length ) ;
129+ SeedPrevious ( previous , options ) ;
179130
180- int position = 0 ;
181- long [ ] previous = new long [ width ] ;
182- long [ ] values = new long [ width ] ;
131+ int stackLimit = _options . StackAllocLimit ;
132+ Span < char > buffer = stackalloc char [ stackLimit ] ;
133+ char [ ] ? rented = null ;
183134
184- SeedPrevious ( previous , options ) ;
135+ int position = 0 ;
136+ bool anyItemProcessed = false ;
185137
186138 try {
187- for ( int i = 0 ; i < coordinates . Length ; i ++ ) {
139+ foreach ( TValue item in coordinates ) {
188140 cancellationToken . ThrowIfCancellationRequested ( ) ;
189-
190- _formatter . GetValues ( coordinates [ i ] , values . AsSpan ( ) ) ;
141+ anyItemProcessed = true ;
142+ _formatter . GetValues ( item , values ) ;
191143
192144 for ( int j = 0 ; j < width ; j ++ ) {
193145 long current = values [ j ] ;
194146 long delta = current - previous [ j ] ;
195147 previous [ j ] = current ;
196148
197- if ( ! PolylineEncoding . TryWriteValue ( delta , buffer , ref position ) ) {
198- _logger . LogOperationFailedDebug ( OperationName ) ;
199- _logger . LogCannotWriteValueToBufferWarning ( position , i ) ;
200- ExceptionGuard . ThrowCouldNotWriteEncodedValueToBuffer ( ) ;
149+ while ( ! PolylineEncoding . TryWriteValue ( delta , buffer , ref position ) ) {
150+ int newSize = rented is null ? stackLimit * 2 : rented . Length * 2 ;
151+ char [ ] newRented = ArrayPool < char > . Shared . Rent ( newSize ) ;
152+ buffer [ ..position ] . CopyTo ( newRented ) ;
153+ if ( rented is not null ) {
154+ ArrayPool < char > . Shared . Return ( rented ) ;
155+ }
156+ rented = newRented ;
157+ buffer = rented . AsSpan ( ) ;
201158 }
202159 }
203160 }
204161
162+ if ( ! anyItemProcessed ) {
163+ _logger . LogOperationFailedDebug ( OperationName ) ;
164+ _logger . LogEmptyArgumentWarning ( nameof ( coordinates ) ) ;
165+ ExceptionGuard . ThrowArgumentCannotBeEmptyEnumerationMessage ( nameof ( coordinates ) ) ;
166+ }
167+
205168 string encodedResult = buffer [ ..position ] . ToString ( ) ;
206169
207170 _logger . LogOperationFinishedDebug ( OperationName ) ;
208171
209172 return _formatter . Write ( encodedResult . AsMemory ( ) ) ;
210173 } finally {
211- if ( temp is not null ) {
212- ArrayPool < char > . Shared . Return ( temp ) ;
174+ if ( rented is not null ) {
175+ ArrayPool < char > . Shared . Return ( rented ) ;
213176 }
214177 }
215178 }
216179
217- private void SeedPrevious ( long [ ] previous , PolylineEncodingOptions < TValue > ? options ) {
218- int width = _formatter . Width ;
219-
180+ private void SeedPrevious ( Span < long > previous , PolylineEncodingOptions < TValue > ? options ) {
220181 if ( options is { HasPrevious : true } ) {
221- _formatter . GetValues ( options . Previous , previous . AsSpan ( ) ) ;
182+ _formatter . GetValues ( options . Previous , previous ) ;
222183 } else {
223- for ( int j = 0 ; j < width ; j ++ ) {
184+ for ( int j = 0 ; j < previous . Length ; j ++ ) {
224185 previous [ j ] = _formatter . GetBaseline ( j ) ;
225186 }
226187 }
227188 }
228-
229- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
230- private static int GetMaxBufferLength ( int count , int valuesPerItem ) {
231- Debug . Assert ( count > 0 , "Count must be greater than zero." ) ;
232- Debug . Assert ( valuesPerItem > 0 , "Values per item must be greater than zero." ) ;
233-
234- int requestedBufferLength = count * valuesPerItem * Defaults . Polyline . Block . Length . Max ;
235-
236- Debug . Assert ( requestedBufferLength > 0 , "Requested buffer length must be greater than zero." ) ;
237-
238- return requestedBufferLength ;
239- }
240189}
0 commit comments