flocking 0.1.0__cp39-abi3-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flocking/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ """Rust-owned vehicle simulation with coherent snapshots and queued commands.
2
+
3
+ Command IDs acknowledge queue admission. State changes become visible at tick
4
+ commit. Stop worlds explicitly (or use a context manager) to join their worker.
5
+ NumPy is optional: install ``flocking[numpy]`` for heightmaps and array snapshots.
6
+ """
7
+
8
+ from ._native import (
9
+ CapacityError, CollisionError, Deadlock, DynamicObstacle, FlockingError,
10
+ FormationConfig, FormationMemberStatus, FormationStatus, InvalidArgumentError,
11
+ LifecycleError, NavigationStatus, QueueFullError, RecoveryConfig, RecoveryStatus, SafetyStatus, Snapshot, StateFrame,
12
+ StoppedError, TimeoutError, UnknownIdError, UnsupportedError, VehicleControl,
13
+ VehicleProfile, WorkerError, World,
14
+ )
15
+
16
+ __all__ = [
17
+ "CapacityError", "CollisionError", "Deadlock", "DynamicObstacle", "FlockingError",
18
+ "FormationConfig", "FormationMemberStatus", "FormationStatus", "InvalidArgumentError",
19
+ "LifecycleError", "NavigationStatus", "QueueFullError", "RecoveryConfig", "RecoveryStatus", "SafetyStatus", "Snapshot", "StateFrame",
20
+ "StoppedError", "TimeoutError", "UnknownIdError", "UnsupportedError", "VehicleControl",
21
+ "VehicleProfile", "WorkerError", "World",
22
+ ]
flocking/_native.pyd ADDED
Binary file
flocking/_native.pyi ADDED
@@ -0,0 +1,340 @@
1
+ """Public native API. Coordinates are meters, angles radians, times seconds."""
2
+ from collections.abc import Iterable, Sequence
3
+ from types import TracebackType
4
+ from typing import Any, Literal, Optional, TypedDict, Union
5
+
6
+ import numpy as np
7
+ from numpy.typing import ArrayLike, NDArray
8
+
9
+ class FlockingError(RuntimeError): ...
10
+ class LifecycleError(FlockingError): ...
11
+ class QueueFullError(FlockingError): ...
12
+ class StoppedError(FlockingError): ...
13
+ class WorkerError(FlockingError): ...
14
+ class UnknownIdError(FlockingError): ...
15
+ class InvalidArgumentError(FlockingError): ...
16
+ class CapacityError(FlockingError): ...
17
+ class CollisionError(FlockingError): ...
18
+ class UnsupportedError(FlockingError): ...
19
+ class TimeoutError(FlockingError): ...
20
+
21
+ class VehicleProfile:
22
+ def __init__(self, *, wheelbase: float = 2.7, length: float = 4.5,
23
+ width: float = 1.8, max_steering_angle: float = 0.6,
24
+ max_forward_speed: float = 20.0, max_reverse_speed: float = 5.0,
25
+ max_acceleration: float = 3.0, max_braking: float = 8.0,
26
+ max_slope_radians: float = 1.5707963267948966) -> None: ...
27
+ @property
28
+ def wheelbase(self) -> float: ...
29
+ @property
30
+ def length(self) -> float: ...
31
+ @property
32
+ def width(self) -> float: ...
33
+ @property
34
+ def max_steering_angle(self) -> float: ...
35
+ @property
36
+ def max_forward_speed(self) -> float: ...
37
+ @property
38
+ def max_reverse_speed(self) -> float: ...
39
+ @property
40
+ def max_acceleration(self) -> float: ...
41
+ @property
42
+ def max_braking(self) -> float: ...
43
+ @property
44
+ def max_slope_radians(self) -> float: ...
45
+
46
+ class VehicleControl:
47
+ def __init__(self, throttle: float = 0.0, brake: float = 0.0,
48
+ steering: float = 0.0, reverse: bool = False) -> None: ...
49
+ @property
50
+ def throttle(self) -> float: ...
51
+ @property
52
+ def brake(self) -> float: ...
53
+ @property
54
+ def steering(self) -> float: ...
55
+ @property
56
+ def reverse(self) -> bool: ...
57
+
58
+ class FormationConfig:
59
+ def __init__(self, kind: str = "convoy", *, spacing: float = 3.0,
60
+ max_speed: float = 8.0, target_weight: float = 1.0,
61
+ separation_weight: float = 1.5, alignment_weight: float = 0.25,
62
+ cohesion_weight: float = 0.1, neighbor_radius: float = 25.0,
63
+ separation_radius: float = 8.0, arrival_radius: float = 1.0,
64
+ reassignment_interval: int = 30, reassignment_hysteresis: float = 2.0,
65
+ bottleneck_lookahead: float = 12.0, widening_ticks: int = 30,
66
+ overtaking: bool = False, narrowing: bool = True, max_members: int = 256,
67
+ max_neighbors: int = 64, work_per_tick: int = 65536) -> None: ...
68
+ @property
69
+ def kind(self) -> str: ...
70
+ @property
71
+ def spacing(self) -> float: ...
72
+ @property
73
+ def max_speed(self) -> float: ...
74
+ @property
75
+ def target_weight(self) -> float: ...
76
+ @property
77
+ def separation_weight(self) -> float: ...
78
+ @property
79
+ def alignment_weight(self) -> float: ...
80
+ @property
81
+ def cohesion_weight(self) -> float: ...
82
+ @property
83
+ def neighbor_radius(self) -> float: ...
84
+ @property
85
+ def separation_radius(self) -> float: ...
86
+ @property
87
+ def arrival_radius(self) -> float: ...
88
+ @property
89
+ def reassignment_interval(self) -> int: ...
90
+ @property
91
+ def reassignment_hysteresis(self) -> float: ...
92
+ @property
93
+ def bottleneck_lookahead(self) -> float: ...
94
+ @property
95
+ def widening_ticks(self) -> int: ...
96
+ @property
97
+ def overtaking(self) -> bool: ...
98
+ @property
99
+ def narrowing(self) -> bool: ...
100
+ @property
101
+ def max_members(self) -> int: ...
102
+ @property
103
+ def max_neighbors(self) -> int: ...
104
+ @property
105
+ def work_per_tick(self) -> int: ...
106
+
107
+ class FormationMemberStatus:
108
+ @property
109
+ def vehicle_id(self) -> int: ...
110
+ @property
111
+ def slot(self) -> int: ...
112
+ @property
113
+ def target(self) -> tuple[float, float]: ...
114
+ @property
115
+ def state(self) -> str: ...
116
+
117
+ class FormationStatus:
118
+ @property
119
+ def id(self) -> int: ...
120
+ @property
121
+ def leader_id(self) -> int: ...
122
+ @property
123
+ def kind(self) -> str: ...
124
+ @property
125
+ def narrowed(self) -> bool: ...
126
+ @property
127
+ def resource_limited(self) -> bool: ...
128
+ @property
129
+ def work_used(self) -> int: ...
130
+ @property
131
+ def members(self) -> list[FormationMemberStatus]: ...
132
+
133
+ class SafetyStatus:
134
+ @property
135
+ def vehicle_id(self) -> int: ...
136
+ @property
137
+ def state(self) -> str: ...
138
+ @property
139
+ def blocked_by(self) -> list[str]: ...
140
+ @property
141
+ def blocked_ticks(self) -> int: ...
142
+
143
+ class DynamicObstacle:
144
+ @property
145
+ def id(self) -> int: ...
146
+ @property
147
+ def bounds(self) -> tuple[float, float, float, float]: ...
148
+ @property
149
+ def velocity(self) -> tuple[float, float]: ...
150
+ @property
151
+ def stopped(self) -> bool: ...
152
+
153
+ class Deadlock:
154
+ @property
155
+ def vehicle_ids(self) -> list[int]: ...
156
+ @property
157
+ def dynamic_obstacle_ids(self) -> list[int]: ...
158
+ @property
159
+ def blocked_ticks(self) -> int: ...
160
+
161
+ class NavigationStatus:
162
+ @property
163
+ def vehicle_id(self) -> int: ...
164
+ @property
165
+ def request_id(self) -> Optional[int]: ...
166
+ @property
167
+ def goal(self) -> Optional[tuple[float, float, Optional[float]]]: ...
168
+ @property
169
+ def state(self) -> str: ...
170
+ @property
171
+ def failure(self) -> Optional[str]: ...
172
+ @property
173
+ def route_id(self) -> Optional[int]: ...
174
+ @property
175
+ def progress(self) -> float: ...
176
+ @property
177
+ def attempt(self) -> int: ...
178
+ @property
179
+ def work_units(self) -> int: ...
180
+ @property
181
+ def guard_deferred(self) -> bool: ...
182
+ @property
183
+ def deferred_ticks(self) -> int: ...
184
+ @property
185
+ def cache_hits(self) -> int: ...
186
+
187
+ class SnapshotArrays(TypedDict):
188
+ tick: int
189
+ version: int
190
+ ids: NDArray[np.uint64]
191
+ states: NDArray[np.float32]
192
+ corners: NDArray[np.float32]
193
+
194
+ class RecoveryConfig:
195
+ def __init__(self, *, enabled: bool = False, blocked_ticks: int = 60,
196
+ reverse_ticks: int = 120, cooldown_ticks: int = 60, max_distance: float = 6.0,
197
+ reverse_speed: float = 2.0, max_attempts: int = 3, max_active: int = 16,
198
+ work_per_tick: int = 65536) -> None: ...
199
+ @property
200
+ def enabled(self) -> bool: ...
201
+ @property
202
+ def blocked_ticks(self) -> int: ...
203
+ @property
204
+ def reverse_ticks(self) -> int: ...
205
+ @property
206
+ def cooldown_ticks(self) -> int: ...
207
+ @property
208
+ def max_distance(self) -> float: ...
209
+ @property
210
+ def reverse_speed(self) -> float: ...
211
+ @property
212
+ def max_attempts(self) -> int: ...
213
+ @property
214
+ def max_active(self) -> int: ...
215
+ @property
216
+ def work_per_tick(self) -> int: ...
217
+
218
+ class RecoveryStatus:
219
+ @property
220
+ def vehicle_id(self) -> int: ...
221
+ @property
222
+ def state(self) -> str: ...
223
+ @property
224
+ def attempt(self) -> int: ...
225
+ @property
226
+ def started_tick(self) -> int: ...
227
+ @property
228
+ def distance(self) -> float: ...
229
+
230
+ class Snapshot:
231
+ @property
232
+ def tick(self) -> int: ...
233
+ @property
234
+ def version(self) -> int: ...
235
+ @property
236
+ def terrain_revision(self) -> int: ...
237
+ @property
238
+ def simulated_seconds(self) -> float: ...
239
+ @property
240
+ def vehicles(self) -> list[tuple[int, float, float, float]]: ...
241
+ @property
242
+ def formations(self) -> list[FormationStatus]: ...
243
+ @property
244
+ def recovery(self) -> list[RecoveryStatus]: ...
245
+ @property
246
+ def safety(self) -> list[SafetyStatus]: ...
247
+ @property
248
+ def dynamic_obstacles(self) -> list[DynamicObstacle]: ...
249
+ @property
250
+ def deadlocks(self) -> list[Deadlock]: ...
251
+ @property
252
+ def navigation(self) -> list[NavigationStatus]: ...
253
+ def to_bytes(self) -> bytes: ...
254
+ def numpy(self) -> SnapshotArrays:
255
+ """Immutable owned arrays: ids[N], states[N,8], corners[N,4,2]."""
256
+ ...
257
+
258
+ class StateFrame:
259
+ @staticmethod
260
+ def from_bytes(data: bytes) -> StateFrame: ...
261
+ @property
262
+ def tick(self) -> int: ...
263
+ @property
264
+ def version(self) -> int: ...
265
+ @property
266
+ def simulated_seconds(self) -> float: ...
267
+ @property
268
+ def vehicles(self) -> list[tuple[int, float, float, float]]: ...
269
+ @property
270
+ def dynamic_obstacles(self) -> list[DynamicObstacle]: ...
271
+ def numpy(self) -> SnapshotArrays: ...
272
+ def to_bytes(self) -> bytes: ...
273
+
274
+ Profile = Union[VehicleProfile, Literal["default"], None]
275
+ GoalRow = Union[tuple[int, float, float], tuple[int, float, float, Optional[float]]]
276
+
277
+ class World:
278
+ def __init__(self, width: int = 10000, height: int = 10000,
279
+ tick_hz: int = 30, queue_capacity: int = 1024, *, threads: Optional[int] = None,
280
+ parallel_threshold: int = 128,
281
+ max_slope_radians: float = 0.7853981633974483, terrain_cost_weight: float = 0.0) -> None: ...
282
+ @staticmethod
283
+ def from_heightmap(heights: ArrayLike, meters_per_cell: float = 1.0,
284
+ tick_hz: int = 30, queue_capacity: int = 1024, *, threads: Optional[int] = None,
285
+ parallel_threshold: int = 128,
286
+ max_slope_radians: float = 0.7853981633974483, terrain_cost_weight: float = 0.0, minimum_height: Optional[float] = None) -> World: ...
287
+ def update_heightmap(self, x: int, y: int, heights: ArrayLike) -> int:
288
+ """Admit a bounded absolute sample patch; snapshot.terrain_revision confirms commit."""
289
+ ...
290
+ def set_static_obstacles(self, obstacles: Iterable[tuple[int, float, float, float, float]]) -> int:
291
+ """Replace at most 128 (id,min_x,min_y,max_x,max_y) footprints; snapshot.terrain_revision confirms commit."""
292
+ ...
293
+ def __enter__(self) -> World: ...
294
+ def __exit__(self, exc_type: Optional[type[BaseException]],
295
+ exc_value: Optional[BaseException], traceback: Optional[TracebackType]) -> bool: ...
296
+ @property
297
+ def lifecycle(self) -> str: ...
298
+ @property
299
+ def threads(self) -> int: ...
300
+ @property
301
+ def tick_hz(self) -> int: ...
302
+ @property
303
+ def max_slope_radians(self) -> float: ...
304
+ @property
305
+ def terrain_cost_weight(self) -> float: ...
306
+ def metrics(self) -> dict[str, Any]: ...
307
+ def queue_metrics(self) -> dict[str, Any]: ...
308
+ def start(self) -> None: ...
309
+ def pause(self) -> None: ...
310
+ def resume(self) -> None: ...
311
+ def stop(self) -> None: ...
312
+ def step_once(self) -> None: ...
313
+ def snapshot(self) -> Snapshot: ...
314
+ def wait_for_tick(self, minimum_tick: int, timeout: float = 1.0) -> Snapshot: ...
315
+ def spawn_vehicle(self, x: float, y: float, heading: float = 0.0,
316
+ profile: Profile = None, *, control: Optional[VehicleControl] = None) -> int:
317
+ """Admit spawn and optional initial control together; control uses a second queue slot."""
318
+ ...
319
+ def spawn_vehicles(self, poses: Union[Iterable[tuple[float, float, float]], NDArray[Any]],
320
+ profile: Profile = None) -> list[int]: ...
321
+ def set_vehicle_control(self, id: int, control: VehicleControl) -> None: ...
322
+ def set_vehicle_controls(self, controls: Iterable[tuple[int, VehicleControl]]) -> None: ...
323
+ def go_to(self, id: int, x: float, y: float) -> None: ...
324
+ def request_go_to(self, id: int, x: float, y: float, heading: Optional[float] = None) -> int: ...
325
+ def request_go_to_many(self, goals: Iterable[GoalRow]) -> list[int]: ...
326
+ def cancel_navigation(self, id: int) -> None: ...
327
+ def despawn_vehicle(self, id: int) -> None: ...
328
+ def despawn_vehicles(self, ids: Iterable[int]) -> None: ...
329
+ def set_vehicle_priority(self, id: int, priority: int) -> None: ...
330
+ def configure_recovery(self, config: RecoveryConfig) -> None: ...
331
+ def create_formation(self, leader_id: int, follower_ids: Sequence[int],
332
+ config: Optional[FormationConfig] = None) -> int: ...
333
+ def configure_formation(self, id: int, config: FormationConfig) -> None: ...
334
+ def set_formation_members(self, id: int, follower_ids: Sequence[int]) -> None: ...
335
+ def disband_formation(self, id: int) -> None: ...
336
+ def spawn_dynamic_obstacle(self, min_x: float, min_y: float, max_x: float,
337
+ max_y: float, velocity_x: float = 0.0, velocity_y: float = 0.0) -> int: ...
338
+ def set_dynamic_obstacle_velocity(self, id: int, velocity_x: float,
339
+ velocity_y: float) -> None: ...
340
+ def despawn_dynamic_obstacle(self, id: int) -> None: ...
flocking/py.typed ADDED
File without changes
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: flocking
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: Intended Audience :: Science/Research
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: Topic :: Games/Entertainment :: Simulation
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Classifier: Typing :: Typed
14
+ Requires-Dist: numpy>=1.26 ; extra == 'numpy'
15
+ Provides-Extra: numpy
16
+ License-File: LICENSE-MIT
17
+ License-File: LICENSE-APACHE
18
+ Summary: Multithreaded off-road vehicle navigation, collision safety and formations in Rust, driven from Python
19
+ Keywords: simulation,navigation,pathfinding,flocking,formations,vehicles,game-server
20
+ Home-Page: https://github.com/benahedron/flocking
21
+ Author: Flocking contributors
22
+ License-Expression: MIT OR Apache-2.0
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
25
+ Project-URL: Changelog, https://github.com/benahedron/flocking/blob/main/CHANGELOG.md
26
+ Project-URL: Documentation, https://github.com/benahedron/flocking/blob/main/docs/python-api.md
27
+ Project-URL: Homepage, https://github.com/benahedron/flocking
28
+ Project-URL: Issues, https://github.com/benahedron/flocking/issues
29
+
30
+ # Flocking
31
+
32
+ Flocking moves large numbers of vehicles across open terrain for game servers
33
+ and simulations. A multithreaded Rust core handles off-road route planning,
34
+ vehicle dynamics, collision safety and formations (convoys, rows, wedges and
35
+ flocks). Python drives it through a small queued-command API.
36
+
37
+ ```bash
38
+ pip install flocking # or: pip install "flocking[numpy]"
39
+ ```
40
+
41
+ ```python
42
+ import time
43
+ import flocking
44
+
45
+ with flocking.World(width=500, height=500) as world: # flat 500 m map
46
+ world.start() # Rust worker ticks at 30 Hz
47
+ car = world.spawn_vehicle(x=50.0, y=50.0)
48
+ world.go_to(car, x=120.0, y=90.0) # queued; planned off-road
49
+
50
+ while True:
51
+ snap = world.snapshot() # coherent, non-blocking
52
+ status = next((s for s in snap.navigation if s.vehicle_id == car), None)
53
+ if status and status.state in ("Arrived", "Failed"):
54
+ break
55
+ time.sleep(0.1)
56
+
57
+ vid, x, y, heading = snap.vehicles[0]
58
+ print(f"tick {snap.tick}: vehicle {vid} {status.state} at ({x:.1f}, {y:.1f})")
59
+ ```
60
+
61
+ The simulation runs on its own thread. Commands enter a bounded queue and take
62
+ effect at the next tick, and every `snapshot()` is a consistent, tick-stamped
63
+ view of the world. Python callbacks are never invoked from the simulation, and
64
+ blocking calls release the GIL.
65
+
66
+ ## Heightmaps and bulk control
67
+
68
+ ```python
69
+ import numpy as np
70
+ import flocking
71
+
72
+ xs = np.linspace(0, 6 * np.pi, 1001, dtype=np.float32)
73
+ heights = 20 + 8 * np.sin(xs)[None, :] * np.cos(xs)[:, None] # 1 km, [y, x]
74
+
75
+ with flocking.World.from_heightmap(heights, meters_per_cell=1.0) as world:
76
+ world.pause() # deterministic manual stepping
77
+ ids = world.spawn_vehicles([(100.0, 100.0 + 8 * i, 0.0) for i in range(10)])
78
+ world.request_go_to_many([(i, 600.0, 140.0 + 8 * n) for n, i in enumerate(ids)])
79
+ for _ in range(900): # 30 simulated seconds
80
+ world.step_once()
81
+ arrays = world.snapshot().numpy()
82
+ print(arrays["ids"], arrays["states"][:, :2])
83
+ ```
84
+
85
+ Terrain can be edited at runtime with `update_heightmap()` and
86
+ `set_static_obstacles()`. Each committed revision is an immutable snapshot.
87
+
88
+ ## Features
89
+
90
+ - Worlds up to 10 km × 10 km at 1 m resolution, chunked terrain with slope queries
91
+ - Kinematic bicycle dynamics with footprints, reversing and slope-aware climbing
92
+ - Hierarchical A*, Hybrid A* and Reeds–Shepp route planning with caching
93
+ - Swept collision safety, emergency stops, priorities, deadlock detection and recovery
94
+ - Convoy, row, wedge and flock formations with slot reassignment and bottleneck narrowing
95
+ - Fixed-step worker lifecycle, bulk commands, deterministic replay, NumPy snapshots
96
+
97
+ Wheels are abi3 (CPython 3.9+) for Linux, macOS and Windows. NumPy is optional
98
+ and is needed only for heightmap arrays and `snapshot.numpy()`.
99
+
100
+ ## Links
101
+
102
+ - [Source and 3D debug viewer](https://github.com/benahedron/flocking)
103
+ - [Python API reference](https://github.com/benahedron/flocking/blob/main/docs/python-api.md)
104
+ - [Example scripts](https://github.com/benahedron/flocking/blob/main/docs/examples.md)
105
+ - [Changelog](https://github.com/benahedron/flocking/blob/main/CHANGELOG.md)
106
+
107
+ Licensed under MIT or Apache-2.0, at your option.
108
+
@@ -0,0 +1,10 @@
1
+ flocking/__init__.py,sha256=lgC5uI0uM66cz9PQP_rJVdK2P59DnGXc4VbNkIbfRz8,1245
2
+ flocking/_native.pyd,sha256=d6UkVCCXuBnBW7Xl0mplQC7ZJ9N1mXr8oJn1iaWsLG8,1656832
3
+ flocking/_native.pyi,sha256=PPx0yI04lmFZ8bRuc9-LJ3-5n8I8Kcn-Rj0kc6ic_Ac,12541
4
+ flocking/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ flocking-0.1.0.dist-info/METADATA,sha256=CdUtO3DeAFc4aPI1p9-JWEt25oBaCE-yW_hBeR1308c,4745
6
+ flocking-0.1.0.dist-info/WHEEL,sha256=xw8F9xBWUOgm5ym2S9tihj_bLJOb723bjhA0kdANo2I,95
7
+ flocking-0.1.0.dist-info/licenses/LICENSE-APACHE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
8
+ flocking-0.1.0.dist-info/licenses/LICENSE-MIT,sha256=jk_Ux4ZorHf_chJsGq_8RmKwfGfyF7h3BRvfQc-iVPs,1099
9
+ flocking-0.1.0.dist-info/sboms/flocking-python.cyclonedx.json,sha256=Rr1zgsUxVrEgJz0O2t0ahUE5SnkYhA7ryF-REyhALFk,30299
10
+ flocking-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.16.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-abi3-win_amd64