66"""
77
88import sys
9- from collections .abc import Callable
109from concurrent .futures import ThreadPoolExecutor
1110import threading
12- from typing import Any
11+ from typing import Any , Callable , List , Optional , Tuple , Union
1312
1413import mapbox_earcut as earcut
1514import numpy as np
@@ -23,7 +22,7 @@ def run_threaded(
2322 pass_count : bool = False ,
2423 pass_barrier : bool = False ,
2524 outer_iterations : int = 1 ,
26- prepare_args : Callable [[], list [Any ]] | None = None ,
25+ prepare_args : Optional [ Callable [[], List [Any ]]] = None ,
2726) -> None :
2827 """
2928 Runs a function many times in parallel.
@@ -48,16 +47,19 @@ def run_threaded(
4847 if pass_barrier :
4948 barrier = threading .Barrier (num_threads )
5049 args .append (barrier )
50+ else :
51+ barrier = None
5152 if pass_count :
5253 all_args = [(func , i , * args ) for i in range (num_threads )]
5354 else :
5455 all_args = [(func , * args ) for i in range (num_threads )]
56+
57+ futures = []
5558 try :
56- futures = []
5759 for arg in all_args :
5860 futures .append (tpe .submit (* arg ))
5961 finally :
60- if len (futures ) < num_threads and pass_barrier :
62+ if len (futures ) < num_threads and barrier is not None :
6163 barrier .abort ()
6264 for f in futures :
6365 f .result ()
@@ -68,15 +70,15 @@ def run_threaded(
6870
6971def test_parallel_triangulate_float32_simple () -> None :
7072 """Test that multiple threads can triangulate simple polygons simultaneously."""
71- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 8
73+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 8
7274
7375 def closure (i : int , b : threading .Barrier ) -> None :
7476 # Create per-thread data to avoid sharing arrays
7577 verts = np .array ([[0 , 0 ], [1 , 0 ], [1 , 1 ]], dtype = np .float32 ).reshape (- 1 , 2 )
7678 rings = np .array ([3 ])
7779
7880 # Synchronize all threads to maximize chance of race condition
79- b .wait ()
81+ _ = b .wait ()
8082
8183 # Perform triangulation
8284 result = earcut .triangulate_float32 (verts , rings )
@@ -92,17 +94,17 @@ def closure(i: int, b: threading.Barrier) -> None:
9294 assert result is not None , f"Thread { i } didn't produce a result"
9395 assert result .dtype == np .uint32
9496 assert result .shape == (3 ,)
95- assert np .all (result == expected ), f"Thread { i } got incorrect result"
97+ assert np .array_equal (result , expected ), f"Thread { i } got incorrect result"
9698
9799
98100def test_parallel_triangulate_float64_simple () -> None :
99101 """Test that multiple threads can triangulate with float64 simultaneously."""
100- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 8
102+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 8
101103
102104 def closure (i : int , b : threading .Barrier ) -> None :
103105 verts = np .array ([[0 , 0 ], [1 , 0 ], [1 , 1 ]], dtype = np .float64 ).reshape (- 1 , 2 )
104106 rings = np .array ([3 ])
105- b .wait ()
107+ _ = b .wait ()
106108 result = earcut .triangulate_float64 (verts , rings )
107109 results [i ] = result
108110
@@ -111,17 +113,17 @@ def closure(i: int, b: threading.Barrier) -> None:
111113 expected = np .array ([1 , 2 , 0 ])
112114 for result in results :
113115 assert result is not None
114- assert np .all (result == expected )
116+ assert np .array_equal (result , expected )
115117
116118
117119def test_parallel_triangulate_int32_simple () -> None :
118120 """Test that multiple threads can triangulate with int32 simultaneously."""
119- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 8
121+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 8
120122
121123 def closure (i : int , b : threading .Barrier ) -> None :
122124 verts = np .array ([[0 , 0 ], [1 , 0 ], [1 , 1 ]], dtype = np .int32 ).reshape (- 1 , 2 )
123125 rings = np .array ([3 ])
124- b .wait ()
126+ _ = b .wait ()
125127 result = earcut .triangulate_int32 (verts , rings )
126128 results [i ] = result
127129
@@ -130,17 +132,17 @@ def closure(i: int, b: threading.Barrier) -> None:
130132 expected = np .array ([1 , 2 , 0 ])
131133 for result in results :
132134 assert result is not None
133- assert np .all (result == expected )
135+ assert np .array_equal (result , expected )
134136
135137
136138def test_parallel_triangulate_int64_simple () -> None :
137139 """Test that multiple threads can triangulate with int64 simultaneously."""
138- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 8
140+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 8
139141
140142 def closure (i : int , b : threading .Barrier ) -> None :
141143 verts = np .array ([[0 , 0 ], [1 , 0 ], [1 , 1 ]], dtype = np .int64 ).reshape (- 1 , 2 )
142144 rings = np .array ([3 ])
143- b .wait ()
145+ _ = b .wait ()
144146 result = earcut .triangulate_int64 (verts , rings )
145147 results [i ] = result
146148
@@ -149,15 +151,15 @@ def closure(i: int, b: threading.Barrier) -> None:
149151 expected = np .array ([1 , 2 , 0 ])
150152 for result in results :
151153 assert result is not None
152- assert np .all (result == expected )
154+ assert np .array_equal (result , expected )
153155
154156
155157# Complex polygon thread safety tests
156158
157159
158160def test_parallel_triangulate_square () -> None :
159161 """Test parallel triangulation of a square."""
160- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 16
162+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 16
161163
162164 def closure (i : int , b : threading .Barrier ) -> None :
163165 # Square polygon
@@ -166,7 +168,7 @@ def closure(i: int, b: threading.Barrier) -> None:
166168 ).reshape (- 1 , 2 )
167169 rings = np .array ([4 ])
168170
169- b .wait ()
171+ _ = b .wait ()
170172 result = earcut .triangulate_float32 (verts , rings )
171173 results [i ] = result
172174
@@ -183,7 +185,7 @@ def closure(i: int, b: threading.Barrier) -> None:
183185
184186def test_parallel_triangulate_with_hole () -> None :
185187 """Test parallel triangulation of a polygon with a hole."""
186- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 8
188+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 8
187189
188190 def closure (i : int , b : threading .Barrier ) -> None :
189191 # Outer square
@@ -195,7 +197,7 @@ def closure(i: int, b: threading.Barrier) -> None:
195197 verts = np .vstack ([outer , inner ]).reshape (- 1 , 2 )
196198 rings = np .array ([4 , 8 ]) # First ring ends at 4, second at 8
197199
198- b .wait ()
200+ _ = b .wait ()
199201 result = earcut .triangulate_float32 (verts , rings )
200202 results [i ] = result
201203
@@ -207,20 +209,20 @@ def closure(i: int, b: threading.Barrier) -> None:
207209 for result in results [1 :]:
208210 assert result is not None
209211 assert result .shape == first_result .shape
210- assert np .all (result == first_result )
212+ assert np .array_equal (result , first_result )
211213
212214
213215def test_parallel_triangulate_complex_shape () -> None :
214216 """Test parallel triangulation with a more complex polygon."""
215- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 12
217+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 12
216218
217219 def closure (i : int , b : threading .Barrier ) -> None :
218220 # Hexagon
219221 angles = np .linspace (0 , 2 * np .pi , 7 )[:- 1 ] # 6 points
220222 verts = np .column_stack ([np .cos (angles ), np .sin (angles )]).astype (np .float64 )
221223 rings = np .array ([6 ])
222224
223- b .wait ()
225+ _ = b .wait ()
224226 result = earcut .triangulate_float64 (verts , rings )
225227 results [i ] = result
226228
@@ -245,15 +247,16 @@ def test_high_contention_same_shape() -> None:
245247 to maximize the chance of exposing race conditions.
246248 """
247249 num_threads = 32
248- results : list [ NDArray [np .uint32 ] | None ] = [None ] * num_threads
250+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * num_threads
249251
250252 def closure (i : int , b : threading .Barrier ) -> None :
251253 verts = np .array ([[0 , 0 ], [5 , 0 ], [5 , 5 ], [0 , 5 ]], dtype = np .float32 ).reshape (
252254 - 1 , 2
253255 )
254256 rings = np .array ([4 ])
255257
256- b .wait ()
258+ _ = b .wait ()
259+ result = None
257260 # Run multiple times in each thread
258261 for _ in range (10 ):
259262 result = earcut .triangulate_float32 (verts , rings )
@@ -275,12 +278,12 @@ def closure(i: int, b: threading.Barrier) -> None:
275278
276279def test_mixed_operations () -> None :
277280 """Test mixing different data types in parallel."""
278- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 16
281+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 16
279282
280283 def closure (i : int , b : threading .Barrier ) -> None :
281284 # Each thread uses a different dtype based on its index
282285 dtypes = [np .float32 , np .float64 , np .int32 , np .int64 ]
283- funcs : list [Callable [[Any , Any ], NDArray [np .uint32 ]]] = [
286+ funcs : List [Callable [[Any , Any ], NDArray [np .uint32 ]]] = [
284287 earcut .triangulate_float32 ,
285288 earcut .triangulate_float64 ,
286289 earcut .triangulate_int32 ,
@@ -293,7 +296,7 @@ def closure(i: int, b: threading.Barrier) -> None:
293296 verts = np .array ([[0 , 0 ], [1 , 0 ], [1 , 1 ]], dtype = dtype ).reshape (- 1 , 2 )
294297 rings = np .array ([3 ])
295298
296- b .wait ()
299+ _ = b .wait ()
297300 result = func (verts , rings )
298301 results [i ] = result
299302
@@ -304,12 +307,12 @@ def closure(i: int, b: threading.Barrier) -> None:
304307 expected = np .array ([1 , 2 , 0 ])
305308 for result in results :
306309 assert result is not None
307- assert np .all (result == expected )
310+ assert np .array_equal (result , expected )
308311
309312
310313def test_varying_sizes () -> None :
311314 """Test with varying polygon sizes across threads."""
312- results : list [ NDArray [np .uint32 ] | None ] = [None ] * 20
315+ results : List [ Optional [ NDArray [np .uint32 ]] ] = [None ] * 20
313316
314317 def closure (i : int , b : threading .Barrier ) -> None :
315318 # Create polygons of different sizes based on thread index
@@ -318,7 +321,7 @@ def closure(i: int, b: threading.Barrier) -> None:
318321 verts = np .column_stack ([np .cos (angles ), np .sin (angles )]).astype (np .float32 )
319322 rings = np .array ([num_sides ])
320323
321- b .wait ()
324+ _ = b .wait ()
322325 result = earcut .triangulate_float32 (verts , rings )
323326 results [i ] = result
324327
@@ -342,15 +345,15 @@ def closure(i: int, b: threading.Barrier) -> None:
342345
343346def test_parallel_invalid_rings () -> None :
344347 """Test that multiple threads can handle invalid input simultaneously."""
345- exceptions : list [ ValueError | None ] = [None ] * 8
348+ exceptions : List [ Optional [ ValueError ] ] = [None ] * 8
346349
347350 def closure (i : int , b : threading .Barrier ) -> None :
348351 verts = np .array ([[0 , 0 ], [1 , 0 ], [1 , 1 ]], dtype = np .float32 ).reshape (- 1 , 2 )
349352 rings = np .array ([5 ]) # Invalid: larger than verts
350353
351- b .wait ()
354+ _ = b .wait ()
352355 try :
353- earcut .triangulate_float32 (verts , rings )
356+ _ = earcut .triangulate_float32 (verts , rings )
354357 exceptions [i ] = None
355358 except ValueError as e :
356359 exceptions [i ] = e
@@ -365,7 +368,9 @@ def closure(i: int, b: threading.Barrier) -> None:
365368
366369def test_parallel_mixed_valid_invalid () -> None :
367370 """Test mixing valid and invalid inputs across threads."""
368- results : list [tuple [str , NDArray [np .uint32 ] | ValueError ] | None ] = [None ] * 16
371+ results : List [Optional [Tuple [str , Union [NDArray [np .uint32 ], ValueError ]]]] = [
372+ None
373+ ] * 16
369374
370375 def closure (i : int , b : threading .Barrier ) -> None :
371376 verts = np .array ([[0 , 0 ], [1 , 0 ], [1 , 1 ]], dtype = np .float32 ).reshape (- 1 , 2 )
0 commit comments