@@ -7,13 +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 ;
1413using System . Collections . Generic ;
15- using System . Diagnostics ;
16- using System . Runtime . CompilerServices ;
1714using System . Threading ;
1815
1916/// <summary>
@@ -67,74 +64,18 @@ public PolylineEncoder(PolylineOptions<TValue, TPolyline> options) {
6764 /// <exception cref="ArgumentException">
6865 /// Thrown when <paramref name="coordinates"/> is empty.
6966 /// </exception>
70- /// <exception cref="InvalidOperationException">
71- /// Thrown when the internal encoding buffer cannot accommodate the encoded value.
72- /// </exception>
7367 /// <exception cref="OperationCanceledException">
7468 /// Thrown when <paramref name="cancellationToken"/> is canceled.
7569 /// </exception>
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." ) ]
7671 public TPolyline Encode ( IEnumerable < TValue > coordinates , CancellationToken cancellationToken = default ) {
77- const string OperationName = nameof ( Encode ) ;
78-
79- _logger . LogOperationStartedDebug ( OperationName ) ;
72+ _logger . LogOperationStartedDebug ( nameof ( Encode ) ) ;
8073
8174 if ( coordinates is null ) {
8275 ExceptionGuard . ThrowArgumentNull ( nameof ( coordinates ) ) ;
8376 }
8477
85- IReadOnlyList < TValue > items = coordinates as IReadOnlyList < TValue > ?? [ .. coordinates ] ;
86-
87- if ( items . Count < 1 ) {
88- _logger . LogOperationFailedDebug ( OperationName ) ;
89- _logger . LogEmptyArgumentWarning ( nameof ( coordinates ) ) ;
90- ExceptionGuard . ThrowArgumentCannotBeEmptyEnumerationMessage ( nameof ( coordinates ) ) ;
91- }
92-
93- int width = _formatter . Width ;
94- int length = GetMaxBufferLength ( items . Count , width ) ;
95-
96- char [ ] ? temp = length <= _options . StackAllocLimit
97- ? null
98- : ArrayPool < char > . Shared . Rent ( length ) ;
99-
100- Span < char > buffer = temp is null ? stackalloc char [ length ] : temp . AsSpan ( 0 , length ) ;
101-
102- int position = 0 ;
103- long [ ] previous = new long [ width ] ;
104- long [ ] values = new long [ width ] ;
105-
106- SeedPrevious ( previous , null ) ;
107-
108- try {
109- for ( int i = 0 ; i < items . Count ; i ++ ) {
110- cancellationToken . ThrowIfCancellationRequested ( ) ;
111-
112- _formatter . GetValues ( items [ i ] , values . AsSpan ( ) ) ;
113-
114- for ( int j = 0 ; j < width ; j ++ ) {
115- long current = values [ j ] ;
116- long delta = current - previous [ j ] ;
117- previous [ j ] = current ;
118-
119- if ( ! PolylineEncoding . TryWriteValue ( delta , buffer , ref position ) ) {
120- _logger . LogOperationFailedDebug ( OperationName ) ;
121- _logger . LogCannotWriteValueToBufferWarning ( position , i ) ;
122- ExceptionGuard . ThrowCouldNotWriteEncodedValueToBuffer ( ) ;
123- }
124- }
125- }
126-
127- // Convert to string inside the try block so the buffer is still valid.
128- string encodedResult = buffer [ ..position ] . ToString ( ) ;
129-
130- _logger . LogOperationFinishedDebug ( OperationName ) ;
131-
132- return _formatter . Write ( encodedResult . AsMemory ( ) ) ;
133- } finally {
134- if ( temp is not null ) {
135- ArrayPool < char > . Shared . Return ( temp ) ;
136- }
137- }
78+ return EncodeCore ( coordinates , null , cancellationToken ) ;
13879 }
13980
14081 /// <summary>
@@ -160,96 +101,89 @@ public TPolyline Encode(IEnumerable<TValue> coordinates, CancellationToken cance
160101 /// <exception cref="ArgumentException">
161102 /// Thrown when <paramref name="coordinates"/> is empty.
162103 /// </exception>
163- /// <exception cref="InvalidOperationException">
164- /// Thrown when the internal encoding buffer cannot accommodate the encoded value.
165- /// </exception>
166104 /// <exception cref="OperationCanceledException">
167105 /// Thrown when <paramref name="cancellationToken"/> is canceled.
168106 /// </exception>
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." ) ]
169108 public TPolyline Encode ( IEnumerable < TValue > coordinates , PolylineEncodingOptions < TValue > ? options , CancellationToken cancellationToken ) {
170- const string OperationName = nameof ( Encode ) ;
171-
172- _logger . LogOperationStartedDebug ( OperationName ) ;
109+ _logger . LogOperationStartedDebug ( nameof ( Encode ) ) ;
173110
174111 if ( coordinates is null ) {
175112 ExceptionGuard . ThrowArgumentNull ( nameof ( coordinates ) ) ;
176113 }
177114
178- IReadOnlyList < TValue > items = coordinates as IReadOnlyList < TValue > ?? [ .. coordinates ] ;
115+ return EncodeCore ( coordinates , options , cancellationToken ) ;
116+ }
179117
180- if ( items . Count < 1 ) {
181- _logger . LogOperationFailedDebug ( OperationName ) ;
182- _logger . LogEmptyArgumentWarning ( nameof ( coordinates ) ) ;
183- ExceptionGuard . ThrowArgumentCannotBeEmptyEnumerationMessage ( nameof ( coordinates ) ) ;
184- }
118+ private TPolyline EncodeCore ( IEnumerable < TValue > coordinates , PolylineEncodingOptions < TValue > ? options , CancellationToken cancellationToken ) {
119+ const string OperationName = nameof ( Encode ) ;
120+ const int MaxStackWidth = 8 ;
185121
186122 int width = _formatter . Width ;
187- int length = GetMaxBufferLength ( items . Count , width ) ;
188123
189- char [ ] ? temp = length <= _options . StackAllocLimit
190- ? null
191- : 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 ] ;
192128
193- Span < char > buffer = temp is null ? stackalloc char [ length ] : temp . AsSpan ( 0 , length ) ;
129+ SeedPrevious ( previous , options ) ;
194130
195- int position = 0 ;
196- long [ ] previous = new long [ width ] ;
197- long [ ] values = new long [ width ] ;
131+ int stackLimit = _options . StackAllocLimit ;
132+ Span < char > buffer = stackalloc char [ stackLimit ] ;
133+ char [ ] ? rented = null ;
198134
199- SeedPrevious ( previous , options ) ;
135+ int position = 0 ;
136+ bool hasItems = false ;
200137
201138 try {
202- for ( int i = 0 ; i < items . Count ; i ++ ) {
139+ foreach ( TValue item in coordinates ) {
203140 cancellationToken . ThrowIfCancellationRequested ( ) ;
204-
205- _formatter . GetValues ( items [ i ] , values . AsSpan ( ) ) ;
141+ hasItems = true ;
142+ _formatter . GetValues ( item , values ) ;
206143
207144 for ( int j = 0 ; j < width ; j ++ ) {
208145 long current = values [ j ] ;
209146 long delta = current - previous [ j ] ;
210147 previous [ j ] = current ;
211148
212- if ( ! PolylineEncoding . TryWriteValue ( delta , buffer , ref position ) ) {
213- _logger . LogOperationFailedDebug ( OperationName ) ;
214- _logger . LogCannotWriteValueToBufferWarning ( position , i ) ;
215- 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 ( ) ;
216158 }
217159 }
218160 }
219161
162+ if ( ! hasItems ) {
163+ _logger . LogOperationFailedDebug ( OperationName ) ;
164+ _logger . LogEmptyArgumentWarning ( nameof ( coordinates ) ) ;
165+ ExceptionGuard . ThrowArgumentCannotBeEmptyEnumerationMessage ( nameof ( coordinates ) ) ;
166+ }
167+
220168 string encodedResult = buffer [ ..position ] . ToString ( ) ;
221169
222170 _logger . LogOperationFinishedDebug ( OperationName ) ;
223171
224172 return _formatter . Write ( encodedResult . AsMemory ( ) ) ;
225173 } finally {
226- if ( temp is not null ) {
227- ArrayPool < char > . Shared . Return ( temp ) ;
174+ if ( rented is not null ) {
175+ ArrayPool < char > . Shared . Return ( rented ) ;
228176 }
229177 }
230178 }
231179
232- private void SeedPrevious ( long [ ] previous , PolylineEncodingOptions < TValue > ? options ) {
233- int width = _formatter . Width ;
234-
180+ private void SeedPrevious ( Span < long > previous , PolylineEncodingOptions < TValue > ? options ) {
235181 if ( options is { HasPrevious : true } ) {
236- _formatter . GetValues ( options . Previous , previous . AsSpan ( ) ) ;
182+ _formatter . GetValues ( options . Previous , previous ) ;
237183 } else {
238- for ( int j = 0 ; j < width ; j ++ ) {
184+ for ( int j = 0 ; j < previous . Length ; j ++ ) {
239185 previous [ j ] = _formatter . GetBaseline ( j ) ;
240186 }
241187 }
242188 }
243-
244- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
245- private static int GetMaxBufferLength ( int count , int valuesPerItem ) {
246- Debug . Assert ( count > 0 , "Count must be greater than zero." ) ;
247- Debug . Assert ( valuesPerItem > 0 , "Values per item must be greater than zero." ) ;
248-
249- int requestedBufferLength = count * valuesPerItem * Defaults . Polyline . Block . Length . Max ;
250-
251- Debug . Assert ( requestedBufferLength > 0 , "Requested buffer length must be greater than zero." ) ;
252-
253- return requestedBufferLength ;
254- }
255189}
0 commit comments