1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
|
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using Pathfinding.Drawing;
#if UNITY_5_5_OR_NEWER
using UnityEngine.Profiling;
using Pathfinding.Util;
using Pathfinding.Graphs.Navmesh;
using Pathfinding.Graphs.Util;
using Pathfinding.Jobs;
using Unity.Jobs;
#endif
#if NETFX_CORE
using Thread = Pathfinding.WindowsStore.Thread;
#else
using Thread = System.Threading.Thread;
#endif
[ExecuteInEditMode]
[AddComponentMenu("Pathfinding/AstarPath")]
/// <summary>
/// Core component for the A* Pathfinding System.
/// This class handles all of the pathfinding system, calculates all paths and stores the info.
/// This class is a singleton class, meaning there should only exist at most one active instance of it in the scene.
/// It might be a bit hard to use directly, usually interfacing with the pathfinding system is done through the <see cref="Pathfinding.Seeker"/> class.
/// </summary>
[HelpURL("https://arongranberg.com/astar/documentation/stable/astarpath.html")]
public class AstarPath : VersionedMonoBehaviour {
/// <summary>The version number for the A* Pathfinding Project</summary>
public static readonly System.Version Version = new System.Version(5, 1, 1);
/// <summary>Information about where the package was downloaded</summary>
public enum AstarDistribution { WebsiteDownload, AssetStore, PackageManager };
/// <summary>Used by the editor to guide the user to the correct place to download updates</summary>
public static readonly AstarDistribution Distribution = AstarDistribution.AssetStore;
/// <summary>
/// Which branch of the A* Pathfinding Project is this release.
/// Used when checking for updates so that
/// users of the development versions can get notifications of development
/// updates.
/// </summary>
public static readonly string Branch = "master";
/// <summary>Holds all graph data</summary>
[UnityEngine.Serialization.FormerlySerializedAs("astarData")]
public AstarData data;
/// <summary>
/// Returns the active AstarPath object in the scene.
/// Note: This is only set if the AstarPath object has been initialized (which happens in Awake).
/// </summary>
#if UNITY_4_6 || UNITY_4_3
public static new AstarPath active;
#else
public static AstarPath active;
#endif
/// <summary>Shortcut to Pathfinding.AstarData.graphs</summary>
public NavGraph[] graphs {
get {
return data.graphs;
}
}
#region InspectorDebug
/// <summary>
/// Visualize graphs in the scene view (editor only).
/// [Open online documentation to see images]
/// </summary>
public bool showNavGraphs = true;
/// <summary>
/// Toggle to show unwalkable nodes.
///
/// Note: Only relevant in the editor
///
/// See: <see cref="unwalkableNodeDebugSize"/>
/// </summary>
public bool showUnwalkableNodes = true;
/// <summary>
/// The mode to use for drawing nodes in the sceneview.
///
/// Note: Only relevant in the editor
///
/// See: Pathfinding.GraphDebugMode
/// </summary>
public GraphDebugMode debugMode;
/// <summary>
/// Low value to use for certain <see cref="debugMode"/> modes.
/// For example if <see cref="debugMode"/> is set to G, this value will determine when the node will be completely red.
///
/// Note: Only relevant in the editor
///
/// See: <see cref="debugRoof"/>
/// See: <see cref="debugMode"/>
/// </summary>
public float debugFloor = 0;
/// <summary>
/// High value to use for certain <see cref="debugMode"/> modes.
/// For example if <see cref="debugMode"/> is set to G, this value will determine when the node will be completely green.
///
/// For the penalty debug mode, the nodes will be colored green when they have a penalty less than <see cref="debugFloor"/> and red
/// when their penalty is greater or equal to this value and something between red and green otherwise.
///
/// Note: Only relevant in the editor
///
/// See: <see cref="debugFloor"/>
/// See: <see cref="debugMode"/>
/// </summary>
public float debugRoof = 20000;
/// <summary>
/// If set, the <see cref="debugFloor"/> and <see cref="debugRoof"/> values will not be automatically recalculated.
///
/// Note: Only relevant in the editor
/// </summary>
public bool manualDebugFloorRoof = false;
/// <summary>
/// If enabled, nodes will draw a line to their 'parent'.
/// This will show the search tree for the latest path.
///
/// Note: Only relevant in the editor
///
/// TODO: Add a showOnlyLastPath flag to indicate whether to draw every node or only the ones visited by the latest path.
/// </summary>
public bool showSearchTree = false;
/// <summary>
/// Size of the red cubes shown in place of unwalkable nodes.
///
/// Note: Only relevant in the editor. Does not apply to grid graphs.
/// See: <see cref="showUnwalkableNodes"/>
/// </summary>
public float unwalkableNodeDebugSize = 0.3F;
/// <summary>
/// The amount of debugging messages.
/// Use less debugging to improve performance (a bit) or just to get rid of the Console spamming.
/// Use more debugging (heavy) if you want more information about what the pathfinding scripts are doing.
/// The InGame option will display the latest path log using in-game GUI.
///
/// [Open online documentation to see images]
/// </summary>
public PathLog logPathResults = PathLog.Normal;
#endregion
#region InspectorSettings
/// <summary>
/// Maximum distance to search for nodes.
/// When searching for the nearest node to a point, this is the limit (in world units) for how far away it is allowed to be.
///
/// This is relevant if you try to request a path to a point that cannot be reached and it thus has to search for
/// the closest node to that point which can be reached (which might be far away). If it cannot find a node within this distance
/// then the path will fail.
///
/// [Open online documentation to see images]
///
/// See: Pathfinding.NNConstraint.constrainDistance
/// </summary>
public float maxNearestNodeDistance = 100;
/// <summary>
/// Max Nearest Node Distance Squared.
/// See: <see cref="maxNearestNodeDistance"/>
/// </summary>
public float maxNearestNodeDistanceSqr {
get { return maxNearestNodeDistance*maxNearestNodeDistance; }
}
/// <summary>
/// If true, all graphs will be scanned during Awake.
/// If you disable this, you will have to call <see cref="Scan"/> yourself to enable pathfinding.
/// Alternatively you could load a saved graph from a file.
///
/// If a startup cache has been generated (see save-load-graphs) (view in online documentation for working links), it always takes priority to load that instead of scanning the graphs.
///
/// This can be useful to enable if you want to scan your graphs asynchronously, or if you have a procedural world which has not been created yet
/// at the start of the game.
///
/// See: <see cref="Scan"/>
/// See: <see cref="ScanAsync"/>
/// </summary>
public bool scanOnStartup = true;
/// <summary>
/// Do a full GetNearest search for all graphs.
/// Additional searches will normally only be done on the graph which in the first fast search seemed to have the closest node.
/// With this setting on, additional searches will be done on all graphs since the first check is not always completely accurate.
/// More technically: GetNearestForce on all graphs will be called if true, otherwise only on the one graph which's GetNearest search returned the best node.
/// Usually faster when disabled, but higher quality searches when enabled.
/// Note: For the PointGraph this setting doesn't matter much as it has only one search mode.
/// </summary>
[System.Obsolete("This setting has been removed. It is now always true", true)]
public bool fullGetNearestSearch = false;
/// <summary>
/// Prioritize graphs.
/// Graphs will be prioritized based on their order in the inspector.
/// The first graph which has a node closer than <see cref="prioritizeGraphsLimit"/> will be chosen instead of searching all graphs.
///
/// Deprecated: This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.
/// </summary>
[System.Obsolete("This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.", true)]
public bool prioritizeGraphs = false;
/// <summary>
/// Distance limit for <see cref="prioritizeGraphs"/>.
/// See: <see cref="prioritizeGraphs"/>
///
/// Deprecated: This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.
/// </summary>
[System.Obsolete("This setting has been removed. It was always a bit of a hack. Use NNConstraint.graphMask if you want to choose which graphs are searched.", true)]
public float prioritizeGraphsLimit = 1F;
/// <summary>
/// Reference to the color settings for this AstarPath object.
/// Color settings include for example which color the nodes should be in, in the sceneview.
/// </summary>
public AstarColor colorSettings;
/// <summary>
/// Stored tag names.
/// See: AstarPath.FindTagNames
/// See: AstarPath.GetTagNames
/// </summary>
[SerializeField]
protected string[] tagNames = null;
/// <summary>
/// The distance function to use as a heuristic.
/// The heuristic, often referred to as just 'H' is the estimated cost from a node to the target.
/// Different heuristics affect how the path picks which one to follow from multiple possible with the same length
/// See: <see cref="Pathfinding.Heuristic"/> for more details and descriptions of the different modes.
/// See: <a href="https://en.wikipedia.org/wiki/Admissible_heuristic">Wikipedia: Admissible heuristic</a>
/// See: <a href="https://en.wikipedia.org/wiki/A*_search_algorithm">Wikipedia: A* search algorithm</a>
/// See: <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Wikipedia: Dijkstra's Algorithm</a>
///
/// Warning: Reducing the heuristic scale below 1, or disabling the heuristic, can significantly increase the cpu cost for pathfinding, especially for large graphs.
/// </summary>
public Heuristic heuristic = Heuristic.Euclidean;
/// <summary>
/// The scale of the heuristic.
/// If a value lower than 1 is used, the pathfinder will search more nodes (slower).
/// If 0 is used, the pathfinding algorithm will be reduced to dijkstra's algorithm. This is equivalent to setting <see cref="heuristic"/> to None.
/// If a value larger than 1 is used the pathfinding will (usually) be faster because it expands fewer nodes, but the paths may no longer be the optimal (i.e the shortest possible paths).
///
/// Usually you should leave this to the default value of 1.
///
/// Warning: Reducing the heuristic scale below 1, or disabling the heuristic, can significantly increase the cpu cost for pathfinding, especially for large graphs.
///
/// See: https://en.wikipedia.org/wiki/Admissible_heuristic
/// See: https://en.wikipedia.org/wiki/A*_search_algorithm
/// See: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
/// </summary>
public float heuristicScale = 1F;
/// <summary>
/// Number of pathfinding threads to use.
/// Multithreading puts pathfinding in another thread, this is great for performance on 2+ core computers since the framerate will barely be affected by the pathfinding at all.
/// - None indicates that the pathfinding is run in the Unity thread as a coroutine
/// - Automatic will try to adjust the number of threads to the number of cores and memory on the computer.
/// Less than 512mb of memory or a single core computer will make it revert to using no multithreading.
///
/// It is recommended that you use one of the "Auto" settings that are available.
/// The reason is that even if your computer might be beefy and have 8 cores.
/// Other computers might only be quad core or dual core in which case they will not benefit from more than
/// 1 or 3 threads respectively (you usually want to leave one core for the unity thread).
/// If you use more threads than the number of cores on the computer it is mostly just wasting memory, it will not run any faster.
/// The extra memory usage is not trivially small. Each thread needs to keep a small amount of data for each node in all the graphs.
/// It is not the full graph data but it is proportional to the number of nodes.
/// The automatic settings will inspect the machine it is running on and use that to determine the number of threads so that no memory is wasted.
///
/// The exception is if you only have one (or maybe two characters) active at time. Then you should probably just go with one thread always since it is very unlikely
/// that you will need the extra throughput given by more threads. Keep in mind that more threads primarily increases throughput by calculating different paths on different
/// threads, it will not calculate individual paths any faster.
///
/// Note that if you are modifying the pathfinding core scripts or if you are directly modifying graph data without using any of the
/// safe wrappers (like <see cref="AddWorkItem)"/> multithreading can cause strange errors and pathfinding stopping to work if you are not careful.
/// For basic usage (not modding the pathfinding core) it should be safe.
///
/// Note: WebGL does not support threads at all (since javascript is single-threaded) so no threads will be used on that platform.
///
/// See: CalculateThreadCount
/// </summary>
public ThreadCount threadCount = ThreadCount.One;
/// <summary>
/// Max number of milliseconds to spend each frame for pathfinding.
/// At least 500 nodes will be searched each frame (if there are that many to search).
/// When using multithreading this value is irrelevant.
/// </summary>
public float maxFrameTime = 1F;
/// <summary>
/// Throttle graph updates and batch them to improve performance.
/// If toggled, graph updates will batched and executed less often (specified by <see cref="graphUpdateBatchingInterval)"/>.
///
/// This can have a positive impact on pathfinding throughput since the pathfinding threads do not need
/// to be stopped as often, and it reduces the overhead per graph update.
/// All graph updates are still applied however, they are just batched together so that more of them are
/// applied at the same time.
///
/// However do not use this if you want minimal latency between a graph update being requested
/// and it being applied.
///
/// This only applies to graph updates requested using the <see cref="UpdateGraphs"/> method. Not those requested
/// using <see cref="AddWorkItem"/>.
///
/// If you want to apply graph updates immediately at some point, you can call <see cref="FlushGraphUpdates"/>.
///
/// See: graph-updates (view in online documentation for working links)
/// </summary>
public bool batchGraphUpdates = false;
/// <summary>
/// Minimum number of seconds between each batch of graph updates.
/// If <see cref="batchGraphUpdates"/> is true, this defines the minimum number of seconds between each batch of graph updates.
///
/// This can have a positive impact on pathfinding throughput since the pathfinding threads do not need
/// to be stopped as often, and it reduces the overhead per graph update.
/// All graph updates are still applied however, they are just batched together so that more of them are
/// applied at the same time.
///
/// Do not use this if you want minimal latency between a graph update being requested
/// and it being applied.
///
/// This only applies to graph updates requested using the <see cref="UpdateGraphs"/> method. Not those requested
/// using <see cref="AddWorkItem"/>.
///
/// See: graph-updates (view in online documentation for working links)
/// </summary>
public float graphUpdateBatchingInterval = 0.2F;
#endregion
#region DebugVariables
#if ProfileAstar
/// <summary>
/// How many paths has been computed this run. From application start.
/// Debugging variable
/// </summary>
public static int PathsCompleted = 0;
public static System.Int64 TotalSearchedNodes = 0;
public static System.Int64 TotalSearchTime = 0;
#endif
/// <summary>
/// The time it took for the last call to Scan() to complete.
/// Used to prevent automatically rescanning the graphs too often (editor only)
/// </summary>
public float lastScanTime { get; private set; }
/// <summary>
/// The path to debug using gizmos.
/// This is the path handler used to calculate the last path.
/// It is used in the editor to draw debug information using gizmos.
/// </summary>
[System.NonSerialized]
public PathHandler debugPathData;
/// <summary>The path ID to debug using gizmos</summary>
[System.NonSerialized]
public ushort debugPathID;
/// <summary>
/// Debug string from the last completed path.
/// Will be updated if <see cref="logPathResults"/> == PathLog.InGame
/// </summary>
string inGameDebugPath;
#endregion
#region StatusVariables
/// <summary>
/// Backing field for <see cref="isScanning"/>.
/// Cannot use an auto-property because they cannot be marked with System.NonSerialized.
/// </summary>
[System.NonSerialized]
bool isScanningBacking;
/// <summary>
/// Set while any graphs are being scanned.
/// It will be true up until the FloodFill is done.
///
/// Note: Not to be confused with graph updates.
///
/// Used to better support Graph Update Objects called for example in OnPostScan
///
/// See: IsAnyGraphUpdateQueued
/// See: IsAnyGraphUpdateInProgress
/// </summary>
public bool isScanning { get { return isScanningBacking; } private set { isScanningBacking = value; } }
/// <summary>
/// Number of parallel pathfinders.
/// Returns the number of concurrent processes which can calculate paths at once.
/// When using multithreading, this will be the number of threads, if not using multithreading it is always 1 (since only 1 coroutine is used).
/// See: IsUsingMultithreading
/// </summary>
public int NumParallelThreads {
get {
return pathProcessor.NumThreads;
}
}
/// <summary>
/// Returns whether or not multithreading is used.
/// \exception System.Exception Is thrown when it could not be decided if multithreading was used or not.
/// This should not happen if pathfinding is set up correctly.
/// Note: This uses info about if threads are running right now, it does not use info from the settings on the A* object.
/// </summary>
public bool IsUsingMultithreading {
get {
return pathProcessor.IsUsingMultithreading;
}
}
/// <summary>
/// Returns if any graph updates are waiting to be applied.
/// Note: This is false while the updates are being performed.
/// Note: This does *not* includes other types of work items such as navmesh cutting or anything added by <see cref="AddWorkItem"/>.
/// </summary>
public bool IsAnyGraphUpdateQueued { get { return graphUpdates.IsAnyGraphUpdateQueued; } }
/// <summary>
/// Returns if any graph updates are being calculated right now.
/// Note: This does *not* includes other types of work items such as navmesh cutting or anything added by <see cref="AddWorkItem"/>.
///
/// See: IsAnyWorkItemInProgress
/// </summary>
public bool IsAnyGraphUpdateInProgress { get { return graphUpdates.IsAnyGraphUpdateInProgress; } }
/// <summary>
/// Returns if any work items are in progress right now.
/// Note: This includes pretty much all types of graph updates.
/// Such as normal graph updates, navmesh cutting and anything added by <see cref="AddWorkItem"/>.
/// </summary>
public bool IsAnyWorkItemInProgress { get { return workItems.workItemsInProgress; } }
/// <summary>
/// Returns if this code is currently being exectuted inside a work item.
/// Note: This includes pretty much all types of graph updates.
/// Such as normal graph updates, navmesh cutting and anything added by <see cref="AddWorkItem"/>.
///
/// In contrast to <see cref="IsAnyWorkItemInProgress"/> this is only true when work item code is being executed, it is not
/// true in-between the updates to a work item that takes several frames to complete.
/// </summary>
internal bool IsInsideWorkItem { get { return workItems.workItemsInProgressRightNow; } }
#endregion
#region Callbacks
/// <summary>
/// Called on Awake before anything else is done.
/// This is called at the start of the Awake call, right after <see cref="active"/> has been set, but this is the only thing that has been done.
/// Use this when you want to set up default settings for an AstarPath component created during runtime since some settings can only be changed in Awake
/// (such as multithreading related stuff)
/// <code>
/// // Create a new AstarPath object on Start and apply some default settings
/// public void Start () {
/// AstarPath.OnAwakeSettings += ApplySettings;
/// AstarPath astar = gameObject.AddComponent<AstarPath>();
/// }
///
/// public void ApplySettings () {
/// // Unregister from the delegate
/// AstarPath.OnAwakeSettings -= ApplySettings;
/// // For example threadCount should not be changed after the Awake call
/// // so here's the only place to set it if you create the component during runtime
/// AstarPath.active.threadCount = ThreadCount.One;
/// }
/// </code>
/// </summary>
public static System.Action OnAwakeSettings;
/// <summary>Called for each graph before they are scanned. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.</summary>
public static OnGraphDelegate OnGraphPreScan;
/// <summary>Called for each graph after they have been scanned. All other graphs might not have been scanned yet. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.</summary>
public static OnGraphDelegate OnGraphPostScan;
/// <summary>Called for each path before searching. Be careful when using multithreading since this will be called from a different thread.</summary>
public static OnPathDelegate OnPathPreSearch;
/// <summary>Called for each path after searching. Be careful when using multithreading since this will be called from a different thread.</summary>
public static OnPathDelegate OnPathPostSearch;
/// <summary>Called before starting the scanning. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.</summary>
public static OnScanDelegate OnPreScan;
/// <summary>Called after scanning. This is called before applying links, flood-filling the graphs and other post processing. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.</summary>
public static OnScanDelegate OnPostScan;
/// <summary>Called after scanning has completed fully. This is called as the last thing in the Scan function. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.</summary>
public static OnScanDelegate OnLatePostScan;
/// <summary>Called when any graphs are updated. Register to for example recalculate the path whenever a graph changes. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead.</summary>
public static OnScanDelegate OnGraphsUpdated;
/// <summary>
/// Called when pathID overflows 65536 and resets back to zero.
/// Note: This callback will be cleared every time it is called, so if you want to register to it repeatedly, register to it directly on receiving the callback as well.
/// </summary>
public static System.Action On65KOverflow;
/// <summary>
/// Called right after callbacks on paths have been called.
///
/// A path's callback function runs on the main thread when the path has been calculated.
/// This is done in batches for all paths that have finished their calculation since the last frame.
/// This event will trigger right after a batch of callbacks have been called.
///
/// If you do not want to use individual path callbacks, you can use this instead to poll all pending paths
/// and see which ones have completed. This is better than doing it in e.g. the Update loop, because
/// here you will have a guarantee that all calculated paths are still valid.
/// Immediately after this callback has finished, other things may invalidate calculated paths, like for example
/// graph updates.
///
/// This is used by the ECS integration to update all entities' pending paths, without having to store
/// a callback for each agent, and also to avoid the ECS synchronization overhead that having individual
/// callbacks would entail.
/// </summary>
public static System.Action OnPathsCalculated;
#endregion
#region MemoryStructures
/// <summary>Processes graph updates</summary>
readonly GraphUpdateProcessor graphUpdates;
/// <summary>Holds a hierarchical graph to speed up some queries like if there is a path between two nodes</summary>
internal readonly HierarchicalGraph hierarchicalGraph;
/// <summary>Holds all active off-mesh links</summary>
public readonly OffMeshLinks offMeshLinks;
/// <summary>
/// Handles navmesh cuts.
/// See: <see cref="Pathfinding.NavmeshCut"/>
/// </summary>
public NavmeshUpdates navmeshUpdates = new NavmeshUpdates();
/// <summary>Processes work items</summary>
readonly WorkItemProcessor workItems;
/// <summary>Holds all paths waiting to be calculated and calculates them</summary>
readonly PathProcessor pathProcessor;
/// <summary>Holds global node data that cannot be stored in individual graphs</summary>
internal GlobalNodeStorage nodeStorage;
/// <summary>
/// Global read-write lock for graph data.
///
/// Graph data is always consistent from the main-thread's perspective, but if you are using jobs to read from graph data, you may need this.
///
/// A write lock is held automatically...
/// - During graph updates. During async graph updates, the lock is only held once per frame while the graph update is actually running, not for the whole duration.
/// - During work items. Async work items work similarly to graph updates, the lock is only held once per frame while the work item is actually running.
/// - When <see cref="GraphModifier"/> events run.
/// - When graph related callbacks, such as <see cref="OnGraphsUpdated"/>, run.
/// - During the last step of a graph's scanning process. See <see cref="ScanningStage"/>.
///
/// To use e.g. AstarPath.active.GetNearest from an ECS job, you'll need to acquire a read lock first, and make sure the lock is only released when the job is finished.
///
/// <code>
/// var readLock = AstarPath.active.LockGraphDataForReading();
/// var handle = new MyJob {
/// // ...
/// }.Schedule(readLock.dependency);
/// readLock.UnlockAfter(handle);
/// </code>
///
/// See: <see cref="LockGraphDataForReading"/>
/// </summary>
RWLock graphDataLock = new RWLock();
bool graphUpdateRoutineRunning = false;
/// <summary>Makes sure QueueGraphUpdates will not queue multiple graph update orders</summary>
bool graphUpdatesWorkItemAdded = false;
/// <summary>
/// Time the last graph update was done.
/// Used to group together frequent graph updates to batches
/// </summary>
float lastGraphUpdate = -9999F;
/// <summary>Held if any work items are currently queued</summary>
PathProcessor.GraphUpdateLock workItemLock;
/// <summary>Holds all completed paths waiting to be returned to where they were requested</summary>
internal readonly PathReturnQueue pathReturnQueue;
/// <summary>
/// Holds settings for heuristic optimization.
/// See: heuristic-opt (view in online documentation for working links)
/// </summary>
public EuclideanEmbedding euclideanEmbedding = new EuclideanEmbedding();
#endregion
/// <summary>
/// Shows or hides graph inspectors.
/// Used internally by the editor
/// </summary>
public bool showGraphs = false;
/// <summary>
/// The next unused Path ID.
/// Incremented for every call to GetNextPathID
/// </summary>
private ushort nextFreePathID = 1;
private AstarPath () {
pathReturnQueue = new PathReturnQueue(this, () => {
if (OnPathsCalculated != null) OnPathsCalculated();
});
// Make sure that the pathProcessor and node storage is never null
nodeStorage = new GlobalNodeStorage(this);
hierarchicalGraph = new HierarchicalGraph(nodeStorage);
pathProcessor = new PathProcessor(this, pathReturnQueue, 1, false);
offMeshLinks = new OffMeshLinks(this);
workItems = new WorkItemProcessor(this);
graphUpdates = new GraphUpdateProcessor(this);
navmeshUpdates.astar = this;
data = new AstarData(this);
// Forward graphUpdates.OnGraphsUpdated to AstarPath.OnGraphsUpdated
workItems.OnGraphsUpdated += () => {
if (OnGraphsUpdated != null) {
try {
OnGraphsUpdated(this);
} catch (System.Exception e) {
Debug.LogException(e);
}
}
};
pathProcessor.OnPathPreSearch += path => {
var tmp = OnPathPreSearch;
if (tmp != null) tmp(path);
};
pathProcessor.OnPathPostSearch += path => {
LogPathResults(path);
var tmp = OnPathPostSearch;
if (tmp != null) tmp(path);
};
// Sent every time the path queue is unblocked
pathProcessor.OnQueueUnblocked += () => {
if (euclideanEmbedding.dirty) {
euclideanEmbedding.RecalculateCosts();
}
};
}
/// <summary>
/// Returns tag names.
/// Makes sure that the tag names array is not null and of length 32.
/// If it is null or not of length 32, it creates a new array and fills it with 0,1,2,3,4 etc...
/// See: AstarPath.FindTagNames
/// </summary>
public string[] GetTagNames () {
if (tagNames == null || tagNames.Length != 32) {
tagNames = new string[32];
for (int i = 0; i < tagNames.Length; i++) {
tagNames[i] = ""+i;
}
tagNames[0] = "Basic Ground";
}
return tagNames;
}
/// <summary>
/// Used outside of play mode to initialize the AstarPath object even if it has not been selected in the inspector yet.
/// This will set the <see cref="active"/> property and deserialize all graphs.
///
/// This is useful if you want to do changes to the graphs in the editor outside of play mode, but cannot be sure that the graphs have been deserialized yet.
/// In play mode this method does nothing.
/// </summary>
public static void FindAstarPath () {
if (Application.isPlaying) return;
if (active == null) active = UnityCompatibility.FindAnyObjectByType<AstarPath>();
if (active != null && (active.data.graphs == null || active.data.graphs.Length == 0)) active.data.DeserializeGraphs();
}
/// <summary>
/// Tries to find an AstarPath object and return tag names.
/// If an AstarPath object cannot be found, it returns an array of length 1 with an error message.
/// See: AstarPath.GetTagNames
/// </summary>
public static string[] FindTagNames () {
FindAstarPath();
return active != null? active.GetTagNames () : new string[1] { "There is no AstarPath component in the scene" };
}
/// <summary>Returns the next free path ID</summary>
internal ushort GetNextPathID () {
if (nextFreePathID == 0) {
nextFreePathID++;
if (On65KOverflow != null) {
System.Action tmp = On65KOverflow;
On65KOverflow = null;
tmp();
}
}
return nextFreePathID++;
}
void RecalculateDebugLimits () {
#if UNITY_EDITOR
debugFloor = float.PositiveInfinity;
debugRoof = float.NegativeInfinity;
bool ignoreSearchTree = !showSearchTree || debugPathData == null;
UnsafeSpan<GlobalNodeStorage.DebugPathNode> debugPathNodes;
if (debugPathData != null && debugPathData.threadID < active.nodeStorage.pathfindingThreadData.Length) debugPathNodes = active.nodeStorage.pathfindingThreadData[debugPathData.threadID].debugPathNodes;
else debugPathNodes = default;
for (int i = 0; i < graphs.Length; i++) {
if (graphs[i] != null && graphs[i].drawGizmos) {
graphs[i].GetNodes(node => {
if (node.Walkable && (ignoreSearchTree || Pathfinding.Util.GraphGizmoHelper.InSearchTree(node, debugPathNodes, debugPathID))) {
float value;
if (debugMode == GraphDebugMode.Penalty) {
value = node.Penalty;
} else if (debugPathNodes.Length > 0) {
var rnode = debugPathNodes[node.NodeIndex];
switch (debugMode) {
case GraphDebugMode.F:
value = rnode.g + rnode.h;
break;
case GraphDebugMode.G:
value = rnode.g;
break;
default:
case GraphDebugMode.H:
value = rnode.h;
break;
}
} else {
value = 0;
}
debugFloor = Mathf.Min(debugFloor, value);
debugRoof = Mathf.Max(debugRoof, value);
}
});
}
}
if (float.IsInfinity(debugFloor)) {
debugFloor = 0;
debugRoof = 1;
}
// Make sure they are not identical, that will cause the color interpolation to fail
if (debugRoof-debugFloor < 1) debugRoof += 1;
#else
debugFloor = 0;
debugRoof = 1;
#endif
}
RedrawScope redrawScope;
/// <summary>Calls OnDrawGizmos on graph generators</summary>
public override void DrawGizmos () {
if (active != this || graphs == null) {
return;
}
colorSettings.PushToStatic(this);
if (!redrawScope.isValid) redrawScope = DrawingManager.GetRedrawScope(gameObject);
if (!workItems.workItemsInProgress && !isScanning) {
// When updating graphs, graph info might not be valid,
// and we cannot render anything during those frames.
// Therefore we use a redraw scope which will continue drawing
// until we dispose it.
redrawScope.Rewind();
if (showNavGraphs && !manualDebugFloorRoof) {
RecalculateDebugLimits();
}
Profiler.BeginSample("Graph.OnDrawGizmos");
// Loop through all graphs and draw their gizmos
for (int i = 0; i < graphs.Length; i++) {
if (graphs[i] != null && graphs[i].drawGizmos)
graphs[i].OnDrawGizmos(DrawingManager.instance.gizmos, showNavGraphs, redrawScope);
}
Profiler.EndSample();
if (showNavGraphs) {
euclideanEmbedding.OnDrawGizmos();
if (debugMode == GraphDebugMode.HierarchicalNode) hierarchicalGraph.OnDrawGizmos(DrawingManager.instance.gizmos, redrawScope);
if (debugMode == GraphDebugMode.NavmeshBorderObstacles) hierarchicalGraph.navmeshEdges.OnDrawGizmos(DrawingManager.instance.gizmos, redrawScope);
}
}
}
#if !ASTAR_NO_GUI
/// <summary>
/// Draws the InGame debugging (if enabled)
/// See: <see cref="logPathResults"/> PathLog
/// </summary>
private void OnGUI () {
if (logPathResults == PathLog.InGame && inGameDebugPath != "") {
GUI.Label(new Rect(5, 5, 400, 600), inGameDebugPath);
}
}
#endif
/// <summary>
/// Prints path results to the log. What it prints can be controled using <see cref="logPathResults"/>.
/// See: <see cref="logPathResults"/>
/// See: PathLog
/// See: Pathfinding.Path.DebugString
/// </summary>
private void LogPathResults (Path path) {
if (logPathResults != PathLog.None && (path.error || logPathResults != PathLog.OnlyErrors)) {
string debug = (path as IPathInternals).DebugString(logPathResults);
if (logPathResults == PathLog.InGame) {
inGameDebugPath = debug;
} else if (path.error) {
Debug.LogWarning(debug);
} else {
Debug.Log(debug);
}
}
}
/// <summary>
/// Checks if any work items need to be executed
/// then runs pathfinding for a while (if not using multithreading because
/// then the calculation happens in other threads)
/// and then returns any calculated paths to the
/// scripts that requested them.
///
/// See: PerformBlockingActions
/// See: PathProcessor.TickNonMultithreaded
/// See: PathReturnQueue.ReturnPaths
/// </summary>
private void Update () {
// This class uses the [ExecuteInEditMode] attribute
// So Update is called even when not playing
// Don't do anything when not in play mode
if (!Application.isPlaying) return;
navmeshUpdates.Update();
// Execute blocking actions such as graph updates
// when not scanning
if (!isScanning) {
PerformBlockingActions();
}
// Calculates paths when not using multithreading
if (!pathProcessor.IsUsingMultithreading) pathProcessor.TickNonMultithreaded();
// Return calculated paths
pathReturnQueue.ReturnPaths(true);
}
private void PerformBlockingActions (bool force = false) {
if (workItemLock.Held && pathProcessor.queue.allReceiversBlocked) {
// Return all paths before starting blocking actions
// since these might change the graph and make returned paths invalid (at least the nodes)
pathReturnQueue.ReturnPaths(false);
Profiler.BeginSample("Work Items");
if (workItems.ProcessWorkItemsForUpdate(force)) {
// At this stage there are no more work items, resume pathfinding threads
workItemLock.Release();
}
Profiler.EndSample();
}
}
/// <summary>
/// Add a work item to be processed when pathfinding is paused.
/// Convenience method that is equivalent to
/// <code>
/// AddWorkItem(new AstarWorkItem(callback));
/// </code>
///
/// See: <see cref="AddWorkItem(AstarWorkItem)"/>
/// </summary>
public void AddWorkItem (System.Action callback) {
AddWorkItem(new AstarWorkItem(callback));
}
/// <summary>
/// Add a work item to be processed when pathfinding is paused.
/// Convenience method that is equivalent to
/// <code>
/// AddWorkItem(new AstarWorkItem(callback));
/// </code>
///
/// See: <see cref="AddWorkItem(AstarWorkItem)"/>
/// </summary>
public void AddWorkItem (System.Action<IWorkItemContext> callback) {
AddWorkItem(new AstarWorkItem(callback));
}
/// <summary>
/// Add a work item to be processed when pathfinding is paused.
///
/// The work item will be executed when it is safe to update nodes. This is defined as between the path searches.
/// When using more threads than one, calling this often might decrease pathfinding performance due to a lot of idling in the threads.
/// Not performance as in it will use much CPU power, but performance as in the number of paths per second will probably go down
/// (though your framerate might actually increase a tiny bit).
///
/// You should only call this function from the main unity thread (i.e normal game code).
///
/// <code>
/// AstarPath.active.AddWorkItem(new AstarWorkItem(() => {
/// // Safe to update graphs here
/// var node = AstarPath.active.GetNearest(transform.position).node;
/// node.Walkable = false;
/// }));
/// </code>
///
/// <code>
/// AstarPath.active.AddWorkItem(() => {
/// // Safe to update graphs here
/// var node = AstarPath.active.GetNearest(transform.position).node;
/// node.position = (Int3)transform.position;
/// });
/// </code>
///
/// See: <see cref="FlushWorkItems"/>
/// </summary>
public void AddWorkItem (AstarWorkItem item) {
workItems.AddWorkItem(item);
// Make sure pathfinding is stopped and work items are processed
if (!workItemLock.Held) {
workItemLock = PausePathfindingSoon();
}
#if UNITY_EDITOR
// If not playing, execute instantly
if (!Application.isPlaying) {
FlushWorkItems();
}
#endif
}
#region GraphUpdateMethods
/// <summary>
/// Will apply queued graph updates as soon as possible, regardless of <see cref="batchGraphUpdates"/>.
/// Calling this multiple times will not create multiple callbacks.
/// This function is useful if you are limiting graph updates, but you want a specific graph update to be applied as soon as possible regardless of the time limit.
/// Note that this does not block until the updates are done, it merely bypasses the <see cref="batchGraphUpdates"/> time limit.
///
/// See: <see cref="FlushGraphUpdates"/>
/// </summary>
public void QueueGraphUpdates () {
if (!graphUpdatesWorkItemAdded) {
graphUpdatesWorkItemAdded = true;
var workItem = graphUpdates.GetWorkItem();
// Add a new work item which first
// sets the graphUpdatesWorkItemAdded flag to false
// and then processes the graph updates
AddWorkItem(new AstarWorkItem(context => {
graphUpdatesWorkItemAdded = false;
lastGraphUpdate = Time.realtimeSinceStartup;
workItem.initWithContext(context);
}, workItem.updateWithContext));
}
}
/// <summary>
/// Waits a moment with updating graphs.
/// If batchGraphUpdates is set, we want to keep some space between them to let pathfinding threads running and then calculate all queued calls at once
/// </summary>
IEnumerator DelayedGraphUpdate () {
graphUpdateRoutineRunning = true;
yield return new WaitForSeconds(graphUpdateBatchingInterval-(Time.realtimeSinceStartup-lastGraphUpdate));
QueueGraphUpdates();
graphUpdateRoutineRunning = false;
}
/// <summary>
/// Update all graphs within bounds after delay seconds.
/// The graphs will be updated as soon as possible.
///
/// See: FlushGraphUpdates
/// See: batchGraphUpdates
/// See: graph-updates (view in online documentation for working links)
/// </summary>
public void UpdateGraphs (Bounds bounds, float delay) {
UpdateGraphs(new GraphUpdateObject(bounds), delay);
}
/// <summary>
/// Update all graphs using the GraphUpdateObject after delay seconds.
/// This can be used to, e.g make all nodes in a region unwalkable, or set them to a higher penalty.
///
/// See: FlushGraphUpdates
/// See: batchGraphUpdates
/// See: graph-updates (view in online documentation for working links)
/// </summary>
public void UpdateGraphs (GraphUpdateObject ob, float delay) {
StartCoroutine(UpdateGraphsInternal(ob, delay));
}
/// <summary>Update all graphs using the GraphUpdateObject after delay seconds</summary>
IEnumerator UpdateGraphsInternal (GraphUpdateObject ob, float delay) {
yield return new WaitForSeconds(delay);
UpdateGraphs(ob);
}
/// <summary>
/// Update all graphs within bounds.
/// The graphs will be updated as soon as possible.
///
/// This is equivalent to
/// <code>
/// UpdateGraphs(new GraphUpdateObject(bounds));
/// </code>
///
/// See: FlushGraphUpdates
/// See: batchGraphUpdates
/// See: graph-updates (view in online documentation for working links)
/// </summary>
public void UpdateGraphs (Bounds bounds) {
UpdateGraphs(new GraphUpdateObject(bounds));
}
/// <summary>
/// Update all graphs using the GraphUpdateObject.
/// This can be used to, e.g make all nodes in a region unwalkable, or set them to a higher penalty.
/// The graphs will be updated as soon as possible (with respect to <see cref="batchGraphUpdates)"/>
///
/// See: FlushGraphUpdates
/// See: batchGraphUpdates
/// See: graph-updates (view in online documentation for working links)
/// </summary>
public void UpdateGraphs (GraphUpdateObject ob) {
if (ob.internalStage != GraphUpdateObject.STAGE_CREATED) {
throw new System.Exception("You are trying to update graphs using the same graph update object twice. Please create a new GraphUpdateObject instead.");
}
ob.internalStage = GraphUpdateObject.STAGE_PENDING;
graphUpdates.AddToQueue(ob);
// If we should limit graph updates, start a coroutine which waits until we should update graphs
if (batchGraphUpdates && Time.realtimeSinceStartup-lastGraphUpdate < graphUpdateBatchingInterval) {
if (!graphUpdateRoutineRunning) {
StartCoroutine(DelayedGraphUpdate());
}
} else {
// Otherwise, graph updates should be carried out as soon as possible
QueueGraphUpdates();
}
}
/// <summary>
/// Forces graph updates to complete in a single frame.
/// This will force the pathfinding threads to finish calculating the path they are currently calculating (if any) and then pause.
/// When all threads have paused, graph updates will be performed.
/// Warning: Using this very often (many times per second) can reduce your fps due to a lot of threads waiting for one another.
/// But you probably wont have to worry about that.
///
/// Note: This is almost identical to <see cref="FlushWorkItems"/>, but added for more descriptive name.
/// This function will also override any time limit delays for graph updates.
/// This is because graph updates are implemented using work items.
/// So calling this function will also execute any other work items (if any are queued).
///
/// Will not do anything if there are no graph updates queued (not even execute other work items).
/// </summary>
public void FlushGraphUpdates () {
if (IsAnyGraphUpdateQueued || IsAnyGraphUpdateInProgress) {
QueueGraphUpdates();
FlushWorkItems();
}
}
#endregion
/// <summary>
/// Forces work items to complete in a single frame.
/// This will force all work items to run immidiately.
/// This will force the pathfinding threads to finish calculating the path they are currently calculating (if any) and then pause.
/// When all threads have paused, work items will be executed (which can be e.g graph updates).
///
/// Warning: Using this very often (many times per second) can reduce your fps due to a lot of threads waiting for one another.
/// But you probably wont have to worry about that
///
/// Note: This is almost (note almost) identical to <see cref="FlushGraphUpdates"/>, but added for more descriptive name.
///
/// Will not do anything if there are no queued work items waiting to run.
/// </summary>
public void FlushWorkItems () {
if (workItems.anyQueued || workItems.workItemsInProgress) {
var graphLock = PausePathfinding();
PerformBlockingActions(true);
graphLock.Release();
}
}
/// <summary>
/// Calculates number of threads to use.
/// If count is not Automatic, simply returns count casted to an int.
/// Returns: An int specifying how many threads to use, 0 means a coroutine should be used for pathfinding instead of a separate thread.
///
/// If count is set to Automatic it will return a value based on the number of processors and memory for the current system.
/// If memory is <= 512MB or logical cores are <= 1, it will return 0. If memory is <= 1024 it will clamp threads to max 2.
/// Otherwise it will return the number of logical cores clamped to 6.
///
/// When running on WebGL this method always returns 0
/// </summary>
public static int CalculateThreadCount (ThreadCount count) {
#if UNITY_WEBGL
return 0;
#else
if (count == ThreadCount.AutomaticLowLoad || count == ThreadCount.AutomaticHighLoad) {
#if ASTARDEBUG
Debug.Log(SystemInfo.systemMemorySize + " " + SystemInfo.processorCount + " " + SystemInfo.processorType);
#endif
int logicalCores = Mathf.Max(1, SystemInfo.processorCount);
int memory = SystemInfo.systemMemorySize;
if (memory <= 0) {
Debug.LogError("Machine reporting that is has <= 0 bytes of RAM. This is definitely not true, assuming 1 GiB");
memory = 1024;
}
if (logicalCores <= 1) return 0;
if (memory <= 512) return 0;
if (count == ThreadCount.AutomaticHighLoad) {
if (memory <= 1024) logicalCores = System.Math.Min(logicalCores, 2);
} else {
//Always run at at most processorCount-1 threads (one core reserved for unity thread).
// Many computers use hyperthreading, so dividing by two is used to remove the hyperthreading cores, pathfinding
// doesn't scale well past the number of physical cores anyway
logicalCores /= 2;
logicalCores = Mathf.Max(1, logicalCores);
if (memory <= 1024) logicalCores = System.Math.Min(logicalCores, 2);
logicalCores = System.Math.Min(logicalCores, 6);
}
return logicalCores;
} else {
int val = (int)count;
return val;
}
#endif
}
/// <summary>Initializes the <see cref="pathProcessor"/> field</summary>
void InitializePathProcessor () {
int numThreads = CalculateThreadCount(threadCount);
// Outside of play mode everything is synchronous, so no threads are used.
if (!Application.isPlaying) numThreads = 0;
int numProcessors = Mathf.Max(numThreads, 1);
bool multithreaded = numThreads > 0;
pathProcessor.StopThreads();
pathProcessor.SetThreadCount(numProcessors, multithreaded);
}
/// <summary>Does simple error checking</summary>
internal void VerifyIntegrity () {
if (data.graphs == null) {
data.graphs = new NavGraph[0];
data.UpdateShortcuts();
}
}
/// <summary>\cond internal</summary>
/// <summary>
/// Internal method to make sure <see cref="active"/> is set to this object and that <see cref="data"/> is not null.
/// Also calls OnEnable for the <see cref="colorSettings"/> and initializes data.userConnections if it wasn't initialized before
///
/// Warning: This is mostly for use internally by the system.
/// </summary>
public void ConfigureReferencesInternal () {
colorSettings = colorSettings ?? new AstarColor();
colorSettings.PushToStatic(this);
}
/// <summary>\endcond</summary>
/// <summary>
/// Initializes the AstarData class.
/// Searches for graph types, calls Awake on <see cref="data"/> and on all graphs
///
/// See: AstarData.FindGraphTypes
/// </summary>
void InitializeGraphs () {
data.FindGraphTypes();
data.OnEnable();
data.UpdateShortcuts();
}
void ShutdownPathfindingThreads () {
// Block until the pathfinding threads have
// completed their current path calculation
var graphLock = PausePathfinding();
navmeshUpdates.OnDisable();
euclideanEmbedding.dirty = false;
// Discard all queued graph updates. Graph updates that are already in progress will still be allowed to finish,
// as they may be allocating unmanaged data which we don't know how to safely deallocate.
graphUpdates.DiscardQueued();
// TODO: Add unit test that verifies that work items that are added will always complete
// Ensure work items complete before disabling this component.
// This is important because work items may allocate temporary unmanaged memory, so we cannot just forget about them.
FlushWorkItems();
if (logPathResults == PathLog.Heavy)
Debug.Log("Processing Possible Work Items");
// Try to join pathfinding threads
pathProcessor.StopThreads();
if (logPathResults == PathLog.Heavy)
Debug.Log("Returning Paths");
// Return all paths
pathReturnQueue.ReturnPaths(false);
graphLock.Release();
euclideanEmbedding.OnDisable();
}
bool hasScannedGraphAtStartup = false;
/// <summary>
/// Called after this component is enabled.
///
/// Unless the component has already been activated in Awake, this method should:
/// - Ensure the singleton holds (setting <see cref="active"/> to this).
/// - Make sure all subsystems that were disabled in OnDisable are again enabled.
/// - This includes starting pathfinding threads.
/// </summary>
void OnEnable () {
// If the component gets re-enabled during runtime.
// Note that the first time the component loads, then Awake will run first
// and will already have set the #active field.
// In the editor, OnDisable -> OnEnable will be called when an undo or redo event happens (both in and outside of play mode).
if (active != null) {
if (active != this && Application.isPlaying) {
if (this.enabled) {
Debug.LogWarning("Another A* component is already in the scene. More than one A* component cannot be active at the same time. Disabling this one.", this);
}
enabled = false;
}
return;
}
// Very important to set this. Ensures the singleton pattern holds
active = this;
// Disable GUILayout to gain some performance, it is not used in the OnGUI call
useGUILayout = false;
if (OnAwakeSettings != null) {
OnAwakeSettings();
}
hierarchicalGraph.OnEnable();
// To make sure all graph modifiers have been enabled before scan (to avoid script execution order issues)
GraphModifier.FindAllModifiers();
RelevantGraphSurface.FindAllGraphSurfaces();
ConfigureReferencesInternal();
// This will load the graph settings, or whole initialized graphs from the cache, if one has been supplied.
data.OnEnable();
// Flush work items, possibly added when loading the graph data
FlushWorkItems();
euclideanEmbedding.dirty = true;
InitializePathProcessor();
// This class uses the [ExecuteInEditMode] attribute
// So OnEnable is called even when not playing
// Don't scan the graphs unless we are in play mode
if (Application.isPlaying) {
navmeshUpdates.OnEnable();
// Scan the graphs if #scanOnStartup is enabled, and we have not loaded a graph cache already.
// We only do this the first time the AstarPath component is enabled.
if (scanOnStartup && !hasScannedGraphAtStartup && (!data.cacheStartup || data.file_cachedStartup == null)) {
hasScannedGraphAtStartup = true;
Scan();
}
}
}
/// <summary>
/// Cleans up graphs to avoid memory leaks.
///
/// This is called by Unity when:
/// - The component is explicitly disabled in play mode or editor mode.
/// - When the component is about to be destroyed
/// - Including when the game stops
/// - When an undo/redo event takes place (Unity will first disable the component and then enable it again).
///
/// During edit and play mode this method should:
/// - Destroy all node data (but not the graphs themselves)
/// - Dispose all unmanaged data
/// - Shutdown pathfinding threads if they are running (any pending path requests are left in the queue)
/// </summary>
void OnDisable () {
redrawScope.Dispose();
if (active == this) {
// Ensure there are no jobs running that might read or write graph data
graphDataLock.WriteSync().Unlock();
ShutdownPathfindingThreads();
// We need to call dispose data here because in the editor the OnDestroy
// method is not called but OnDisable is. It is vital that graph data
// is destroyed even in the editor (e.g. when going from edit mode to play mode)
// because a lot of data is stored as NativeArrays which need to be disposed.
// There is also another case where this is important. When the unity
// editor is configured to stop play mode after recompiling scripts
// it seems to not call OnDestroy (or at least not reliably across all versions of Unity).
// So we need to ensure we dispose of all the data during OnDisable.
data.DestroyAllNodes();
data.DisposeUnmanagedData();
hierarchicalGraph.OnDisable();
nodeStorage.OnDisable();
offMeshLinks.OnDisable();
active = null;
}
}
/// <summary>
/// Clears up variables and other stuff, destroys graphs.
/// Note that when destroying an AstarPath object, all static variables such as callbacks will be cleared.
/// </summary>
void OnDestroy () {
if (logPathResults == PathLog.Heavy)
Debug.Log("AstarPath Component Destroyed - Cleaning Up Pathfinding Data");
// active has already been set to null during OnDisable.
// We temporarily make this object the active one just during the destruction.
var prevActive = active;
active = this;
ShutdownPathfindingThreads();
pathProcessor.Dispose();
if (logPathResults == PathLog.Heavy)
Debug.Log("Destroying Graphs");
// Clean up graph data
// Data may be null if this object was never enabled because another A* instance existed.
if (data != null) data.OnDestroy();
active = prevActive;
if (logPathResults == PathLog.Heavy)
Debug.Log("Cleaning up variables");
// Clear variables up, static variables are good to clean up, otherwise the next scene might get weird data
if (active == this) {
// Clear all callbacks
OnAwakeSettings = null;
OnGraphPreScan = null;
OnGraphPostScan = null;
OnPathPreSearch = null;
OnPathPostSearch = null;
OnPreScan = null;
OnPostScan = null;
OnLatePostScan = null;
On65KOverflow = null;
OnGraphsUpdated = null;
active = null;
}
}
#region ScanMethods
/// <summary>
/// Allocate a bunch of nodes at once.
/// This is faster than allocating each individual node separately and it can be done in a separate thread by using jobs.
///
/// <code>
/// var nodes = new PointNode[128];
/// var job = AstarPath.active.AllocateNodes(nodes, 128, () => new PointNode(), 1);
///
/// job.Complete();
/// </code>
///
/// See: <see cref="InitializeNode"/>
/// </summary>
/// <param name="result">Node array to fill</param>
/// <param name="count">How many nodes to allocate</param>
/// <param name="createNode">Delegate which creates a node. () => new T(). Note that new T(AstarPath.active) should *not* be used as that will cause the node to be initialized twice.</param>
/// <param name="variantsPerNode">How many variants of the node to allocate. Should be the same as \reflink{GraphNode.PathNodeVariants} for this node type.</param>
public Unity.Jobs.JobHandle AllocateNodes<T>(T[] result, int count, System.Func<T> createNode, uint variantsPerNode) where T : GraphNode {
if (!pathProcessor.queue.allReceiversBlocked) {
throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct");
}
return nodeStorage.AllocateNodesJob(result, count, createNode, variantsPerNode);
}
/// <summary>
/// Initializes temporary path data for a node.
///
/// Use like: InitializeNode(new PointNode())
///
/// See: <see cref="AstarPath.AllocateNodes"/>
/// </summary>
internal void InitializeNode (GraphNode node) {
if (!pathProcessor.queue.allReceiversBlocked) {
throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct");
}
nodeStorage.InitializeNode(node);
}
internal void InitializeNodes (GraphNode[] nodes) {
if (!pathProcessor.queue.allReceiversBlocked) {
throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct");
}
for (int i = 0; i < nodes.Length; i++) nodeStorage.InitializeNode(nodes[i]);
}
/// <summary>
/// Internal method to destroy a given node.
/// This is to be called after the node has been disconnected from the graph so that it cannot be reached from any other nodes.
/// It should only be called during graph updates, that is when the pathfinding threads are either not running or paused.
///
/// Warning: This method should not be called by user code. It is used internally by the system.
/// </summary>
internal void DestroyNode (GraphNode node) {
nodeStorage.DestroyNode(node);
}
/// <summary>
/// Blocks until all pathfinding threads are paused and blocked.
///
/// <code>
/// var graphLock = AstarPath.active.PausePathfinding();
/// // Here we can modify the graphs safely. For example by increasing the penalty of a node
/// AstarPath.active.data.gridGraph.GetNode(0, 0).Penalty += 1000;
///
/// // Allow pathfinding to resume
/// graphLock.Release();
/// </code>
///
/// Returns: A lock object. You need to call <see cref="Pathfinding.PathProcessor.GraphUpdateLock.Release"/> on that object to allow pathfinding to resume.
/// Note: In most cases this should not be called from user code. Use the <see cref="AddWorkItem"/> method instead.
///
/// See: <see cref="AddWorkItem"/>
/// </summary>
public PathProcessor.GraphUpdateLock PausePathfinding () {
// Ensure there are no jobs running that might read or write graph data,
// as this method is typically used right before one modifies graph data.
graphDataLock.WriteSync().Unlock();
return pathProcessor.PausePathfinding(true);
}
/// <summary>
/// Blocks the path queue so that e.g work items can be performed.
///
/// Pathfinding threads will stop accepting new path requests and will finish the ones they are currently calculating asynchronously.
/// When the lock is released, the pathfinding threads will resume as normal.
///
/// Note: You are unlikely to need to use this method. It is primarily for internal use.
/// </summary>
public PathProcessor.GraphUpdateLock PausePathfindingSoon () {
return pathProcessor.PausePathfinding(false);
}
/// <summary>
/// Scans a particular graph.
/// Calling this method will recalculate the specified graph from scratch.
/// This method is pretty slow (depending on graph type and graph complexity of course), so it is advisable to use
/// smaller graph updates whenever possible.
///
/// <code>
/// // Recalculate all graphs
/// AstarPath.active.Scan();
///
/// // Recalculate only the first grid graph
/// var graphToScan = AstarPath.active.data.gridGraph;
/// AstarPath.active.Scan(graphToScan);
///
/// // Recalculate only the first and third graphs
/// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
/// AstarPath.active.Scan(graphsToScan);
/// </code>
///
/// See: graph-updates (view in online documentation for working links)
/// See: ScanAsync
/// </summary>
public void Scan (NavGraph graphToScan) {
if (graphToScan == null) throw new System.ArgumentNullException();
Scan(new NavGraph[] { graphToScan });
}
/// <summary>
/// Scans all specified graphs.
///
/// Calling this method will recalculate all specified graphs (or all graphs if the graphsToScan parameter is null) from scratch.
/// This method is pretty slow (depending on graph type and graph complexity of course), so it is advisable to use
/// smaller graph updates whenever possible.
///
/// <code>
/// // Recalculate all graphs
/// AstarPath.active.Scan();
///
/// // Recalculate only the first grid graph
/// var graphToScan = AstarPath.active.data.gridGraph;
/// AstarPath.active.Scan(graphToScan);
///
/// // Recalculate only the first and third graphs
/// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
/// AstarPath.active.Scan(graphsToScan);
/// </code>
///
/// See: graph-updates (view in online documentation for working links)
/// See: ScanAsync
/// </summary>
/// <param name="graphsToScan">The graphs to scan. If this parameter is null then all graphs will be scanned</param>
public void Scan (NavGraph[] graphsToScan = null) {
var prevStage = (ScanningStage)(-1);
Profiler.BeginSample("Scan");
Profiler.BeginSample("Init");
foreach (var p in ScanInternal(graphsToScan, false)) {
if (prevStage != p.stage) {
Profiler.EndSample();
Profiler.BeginSample(p.stage.ToString());
#if !NETFX_CORE && UNITY_EDITOR
// Log progress to the console
System.Console.WriteLine(p.stage);
#endif
prevStage = p.stage;
}
}
Profiler.EndSample();
Profiler.EndSample();
}
/// <summary>
/// Scans a particular graph asynchronously. This is a IEnumerable, you can loop through it to get the progress
///
/// You can scan graphs asyncronously by yielding when you iterate through the returned IEnumerable.
/// Note that this does not guarantee a good framerate, but it will allow you
/// to at least show a progress bar while scanning.
///
/// <code>
/// IEnumerator Start () {
/// foreach (Progress progress in AstarPath.active.ScanAsync()) {
/// Debug.Log("Scanning... " + progress.ToString());
/// yield return null;
/// }
/// }
/// </code>
///
/// See: Scan
/// </summary>
public IEnumerable<Progress> ScanAsync (NavGraph graphToScan) {
if (graphToScan == null) throw new System.ArgumentNullException();
return ScanAsync(new NavGraph[] { graphToScan });
}
/// <summary>
/// Scans all specified graphs asynchronously. This is a IEnumerable, you can loop through it to get the progress
///
/// You can scan graphs asyncronously by yielding when you loop through the progress.
/// Note that this does not guarantee a good framerate, but it will allow you
/// to at least show a progress bar during scanning.
///
/// <code>
/// IEnumerator Start () {
/// foreach (Progress progress in AstarPath.active.ScanAsync()) {
/// Debug.Log("Scanning... " + progress.ToString());
/// yield return null;
/// }
/// }
/// </code>
///
/// Note: If the graphs are already scanned, doing an async scan will temporarily cause increased memory usage, since two copies of the graphs will be kept in memory during the async scan.
/// This may not be desirable on some platforms. A non-async scan will not cause this temporary increased memory usage.
///
/// See: Scan
/// </summary>
/// <param name="graphsToScan">The graphs to scan. If this parameter is null then all graphs will be scanned</param>
public IEnumerable<Progress> ScanAsync (NavGraph[] graphsToScan = null) {
return ScanInternal(graphsToScan, true);
}
class DummyGraphUpdateContext : IGraphUpdateContext {
public void DirtyBounds (Bounds bounds) {}
}
IEnumerable<Progress> ScanInternal (NavGraph[] graphsToScan, bool async) {
if (graphsToScan == null) graphsToScan = graphs;
if (graphsToScan == null || graphsToScan.Length == 0) {
yield break;
}
if (isScanning) throw new System.InvalidOperationException("Another async scan is already running");
// Guard to ensure the A* object is always enabled if the graphs have any valid data.
// This is because otherwise the OnDisable method will not be called and some unmanaged data
// in NativeArrays may end up leaking.
if (!enabled) throw new System.InvalidOperationException("The AstarPath object must be enabled to scan graphs");
if (active != this) throw new System.InvalidOperationException("The AstarPath object is not enabled in a scene");
isScanning = true;
VerifyIntegrity();
var graphUpdateLock = PausePathfinding();
// Make sure all paths that are in the queue to be returned
// are returned immediately
// Some modifiers (e.g the funnel modifier) rely on
// the nodes being valid when the path is returned
pathReturnQueue.ReturnPaths(false);
// Ensure all graph updates that are in progress get completed immediately.
// Graph updates that are in progress may use graph data, and we don't want to re-scan the graphs under their feet.
workItems.ProcessWorkItemsForScan(true);
if (!Application.isPlaying) {
data.FindGraphTypes();
GraphModifier.FindAllModifiers();
}
yield return new Progress(0.05F, ScanningStage.PreProcessingGraphs);
{
var writeLock2 = graphDataLock.WriteSync();
if (OnPreScan != null) {
OnPreScan(this);
}
GraphModifier.TriggerEvent(GraphModifier.EventType.PreScan);
GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate);
writeLock2.Unlock();
}
data.LockGraphStructure();
// Make sure the physics engine data is up to date.
// Scanning graphs may use physics methods and it is very confusing if they
// do not always pick up the latest changes made to the scene.
Physics.SyncTransforms();
Physics2D.SyncTransforms();
var watch = System.Diagnostics.Stopwatch.StartNew();
// Destroy previous nodes, unless we are doing an async scan.
// We do not want the graphs to be in an invalid state during the async scan,
// so we cannot eagerly destroy them here.
// This means that during an async scan we may have two copies of the graphs in memory.
// Most of the data will be destroyed at the end of the async scan, but some memory will
// still be reserved. So a non-async scan is more memory efficient.
if (!async) {
var writeLock2 = graphDataLock.WriteSync();
Profiler.BeginSample("Destroy previous nodes");
for (int i = 0; i < graphsToScan.Length; i++) {
if (graphsToScan[i] != null) {
((IGraphInternals)graphsToScan[i]).DestroyAllNodes();
}
}
Profiler.EndSample();
writeLock2.Unlock();
}
if (OnGraphPreScan != null) {
var writeLock2 = graphDataLock.WriteSync();
for (int i = 0; i < graphsToScan.Length; i++) {
if (graphsToScan[i] != null) OnGraphPreScan(graphsToScan[i]);
}
writeLock2.Unlock();
}
// Loop through all graphs and start scanning them
var promises = new IGraphUpdatePromise[graphsToScan.Length];
var iterators = new IEnumerator<JobHandle>[graphsToScan.Length];
for (int i = 0; i < graphsToScan.Length; i++) {
if (graphsToScan[i] != null) {
promises[i] = ((IGraphInternals)graphsToScan[i]).ScanInternal(async);
iterators[i] = promises[i].Prepare();
}
}
// Scan all graphs concurrently by progressing all scanning iterators.
// If the graphs use the job system internally (like the grid, recast and navmesh graphs),
// then multiple graphs will even be scanned in parallel.
var it = ProgressScanningIteratorsConcurrently(iterators, promises, async);
while (true) {
try {
if (!it.MoveNext()) break;
} catch {
isScanning = false;
data.UnlockGraphStructure();
graphUpdateLock.Release();
throw;
}
yield return it.Current.MapTo(0.1f, 0.8f);
}
yield return new Progress(0.95f, ScanningStage.FinishingScans);
// Now we proceed with the last step of each graph's scanning process
// This part will make the results of the scan visible to the rest of the game.
// As a consequence, we must make sure to *not* yield anymore after this point,
// since that would make the rest of the game run while the graphs may be in an invalid state.
var writeLock = graphDataLock.WriteSync();
var ctx = new DummyGraphUpdateContext();
for (int i = 0; i < promises.Length; i++) {
try {
if (promises[i] != null) {
Profiler.BeginSample("Finalizing " + graphsToScan[i].GetType().Name);
promises[i].Apply(ctx);
Profiler.EndSample();
}
} catch {
isScanning = false;
data.UnlockGraphStructure();
graphUpdateLock.Release();
writeLock.Unlock();
throw;
}
}
for (int i = 0; i < graphsToScan.Length; i++) {
if (graphsToScan[i] != null) {
if (OnGraphPostScan != null) {
OnGraphPostScan(graphsToScan[i]);
}
// Notify the off mesh links subsystem that graphs have been recalculated, and we may need to recalculate off mesh links.
// But skip this for the link graph, since that's the graph that holds the off mesh link nodes themselves.
if (!(graphsToScan[i] is LinkGraph)) offMeshLinks.DirtyBounds(graphsToScan[i].bounds);
}
}
// Unlock the graph structure here so that e.g. off-mesh-links can add the point graph required for them to work
data.UnlockGraphStructure();
// Graph Modifiers and the OnGraphsUpdated callback may modify graphs arbitrarily, so this also needs to be inside the write lock
if (OnPostScan != null) {
OnPostScan(this);
}
GraphModifier.TriggerEvent(GraphModifier.EventType.PostScan);
// This lock may not be held if there are no work items pending
if (workItemLock.Held) {
Profiler.BeginSample("Work Items");
// Note that this never sends PostUpdate (or similar) events. Those are sent below instead.
workItems.ProcessWorkItemsForScan(true);
Profiler.EndSample();
workItemLock.Release();
}
offMeshLinks.Refresh();
GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdateBeforeAreaRecalculation);
// Recalculate connected components synchronously
hierarchicalGraph.RecalculateIfNecessary();
// Scanning a graph *is* a type of update
GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate);
if (OnGraphsUpdated != null) OnGraphsUpdated(this);
// Signal that we have stopped scanning here
isScanning = false;
if (OnLatePostScan != null) OnLatePostScan(this);
GraphModifier.TriggerEvent(GraphModifier.EventType.LatePostScan);
writeLock.Unlock();
euclideanEmbedding.dirty = true;
euclideanEmbedding.RecalculatePivots();
// Perform any blocking actions
FlushWorkItems();
// Resume pathfinding threads
graphUpdateLock.Release();
watch.Stop();
lastScanTime = (float)watch.Elapsed.TotalSeconds;
if (logPathResults != PathLog.None && logPathResults != PathLog.OnlyErrors) {
Debug.Log("Scanned graphs in " + (lastScanTime*1000).ToString("0") + " ms");
}
}
internal static IEnumerator<Progress> ProgressScanningIteratorsConcurrently (IEnumerator<JobHandle>[] iterators, IGraphUpdatePromise[] promises, bool async) {
while (true) {
int firstNonFinished = -1;
bool mainThreadWork = false;
for (int i = 0; i < iterators.Length; i++) {
var it = iterators[i];
if (it == null) continue;
if (async) {
if (it.Current.IsCompleted) {
// If the job completed (maybe because a real job completed, or because the iterator returned a dummy JobHandle), then it must be doing some work on the main thread.
// In that case, we shouldn't sleep or yield while waiting.
mainThreadWork = true;
it.Current.Complete();
} else {
if (firstNonFinished == -1) firstNonFinished = i;
continue;
}
} else {
it.Current.Complete();
}
Profiler.BeginSample("Preparing");
if (it.MoveNext()) {
if (firstNonFinished == -1) firstNonFinished = i;
} else iterators[i] = null;
Profiler.EndSample();
}
if (firstNonFinished != -1) {
if (async) {
// If main thread work is happening, then we are ok with progressing the iterators as often as possible
if (!mainThreadWork) {
// Ensure that we won't be completely busy spinning if the user waits on an async scan in a tight loop
System.Threading.Thread.Yield();
}
// Just used for progress information
// This graph will advance the progress bar from minp to maxp
float minp = (float)firstNonFinished/iterators.Length;
float maxp = (float)(firstNonFinished+0.95F)/iterators.Length;
yield return new Progress(Mathf.Lerp(minp, maxp, promises[firstNonFinished].Progress), ScanningStage.ScanningGraph, firstNonFinished, iterators.Length);
}
} else {
break;
}
}
}
#endregion
internal void DirtyBounds (Bounds bounds) {
offMeshLinks.DirtyBounds(bounds);
workItems.DirtyGraphs();
}
private static int waitForPathDepth = 0;
/// <summary>
/// Blocks until the path has been calculated.
///
/// Normally it takes a few frames for a path to be calculated and returned.
/// This function will ensure that the path will be calculated when this function returns
/// and that the callback for that path has been called.
///
/// If requesting a lot of paths in one go and waiting for the last one to complete,
/// it will calculate most of the paths in the queue (only most if using multithreading, all if not using multithreading).
///
/// Use this function only if you really need to.
/// There is a point to spreading path calculations out over several frames.
/// It smoothes out the framerate and makes sure requesting a large
/// number of paths at the same time does not cause lag.
///
/// Note: Graph updates and other callbacks might get called during the execution of this function.
///
/// When the pathfinder is shutting down. I.e in OnDestroy, this function will not do anything.
///
/// Throws: Exception if pathfinding is not initialized properly for this scene (most likely no AstarPath object exists)
/// or if the path has not been started yet.
/// Also throws an exception if critical errors occur such as when the pathfinding threads have crashed (which should not happen in normal cases).
/// This prevents an infinite loop while waiting for the path.
///
/// See: Pathfinding.Path.WaitForPath
/// See: Pathfinding.Path.BlockUntilCalculated
/// </summary>
/// <param name="path">The path to wait for. The path must be started, otherwise an exception will be thrown.</param>
public static void BlockUntilCalculated (Path path) {
if (active == null)
throw new System.Exception("Pathfinding is not correctly initialized in this scene (yet?). " +
"AstarPath.active is null.\nDo not call this function in Awake");
if (path == null) throw new System.ArgumentNullException(nameof(path));
if (active.pathProcessor.queue.isClosed) return;
if (path.PipelineState == PathState.Created) {
throw new System.Exception("The specified path has not been started yet.");
}
waitForPathDepth++;
if (waitForPathDepth == 5) {
Debug.LogError("You are calling the BlockUntilCalculated function recursively (maybe from a path callback). Please don't do this.");
}
if (path.PipelineState < PathState.ReturnQueue) {
if (active.IsUsingMultithreading) {
while (path.PipelineState < PathState.ReturnQueue) {
if (active.pathProcessor.queue.isClosed) {
waitForPathDepth--;
throw new System.Exception("Pathfinding Threads seem to have crashed.");
}
// Wait for threads to calculate paths
Thread.Sleep(1);
active.PerformBlockingActions(true);
}
} else {
while (path.PipelineState < PathState.ReturnQueue) {
if (active.pathProcessor.queue.isEmpty && path.PipelineState != PathState.Processing) {
waitForPathDepth--;
throw new System.Exception("Critical error. Path Queue is empty but the path state is '" + path.PipelineState + "'");
}
// Calculate some paths
active.pathProcessor.TickNonMultithreaded();
active.PerformBlockingActions(true);
}
}
}
active.pathReturnQueue.ReturnPaths(false);
waitForPathDepth--;
}
/// <summary>
/// Adds the path to a queue so that it will be calculated as soon as possible.
/// The callback specified when constructing the path will be called when the path has been calculated.
/// Usually you should use the Seeker component instead of calling this function directly.
///
/// <code>
/// // There must be an AstarPath instance in the scene
/// if (AstarPath.active == null) return;
///
/// // We can calculate multiple paths asynchronously
/// for (int i = 0; i < 10; i++) {
/// var path = ABPath.Construct(transform.position, transform.position+transform.forward*i*10, OnPathComplete);
///
/// // Calculate the path by using the AstarPath component directly
/// AstarPath.StartPath(path);
/// }
/// </code>
/// </summary>
/// <param name="path">The path that should be enqueued.</param>
/// <param name="pushToFront">If true, the path will be pushed to the front of the queue, bypassing all waiting paths and making it the next path to be calculated.
/// This can be useful if you have a path which you want to prioritize over all others. Be careful to not overuse it though.
/// If too many paths are put in the front of the queue often, this can lead to normal paths having to wait a very long time before being calculated.</param>
/// <param name="assumeInPlayMode">Typically path.BlockUntilCalculated will be called when not in play mode. However, the play mode check will not work if
/// you call this from a separate thread, or a job. In that case you can set this to true to skip the check.</param>
public static void StartPath (Path path, bool pushToFront = false, bool assumeInPlayMode = false) {
// Copy to local variable to avoid multithreading issues
var astar = active;
if (System.Object.ReferenceEquals(astar, null)) {
Debug.LogError("There is no AstarPath object in the scene or it has not been initialized yet");
return;
}
if (path.PipelineState != PathState.Created) {
throw new System.Exception("The path has an invalid state. Expected " + PathState.Created + " found " + path.PipelineState + "\n" +
"Make sure you are not requesting the same path twice");
}
if (astar.pathProcessor.queue.isClosed) {
path.FailWithError("No new paths are accepted");
return;
}
if (astar.graphs == null || astar.graphs.Length == 0) {
Debug.LogError("There are no graphs in the scene");
path.FailWithError("There are no graphs in the scene");
Debug.LogError(path.errorLog);
return;
}
path.Claim(astar);
// Will increment p.state to PathState.PathQueue
((IPathInternals)path).AdvanceState(PathState.PathQueue);
if (pushToFront) {
astar.pathProcessor.queue.PushFront(path);
} else {
astar.pathProcessor.queue.Push(path);
}
// Outside of play mode, all path requests are synchronous.
// However, inside a job we cannot check this, because Unity will throw an exception.
// But luckily pretty much all jobs will run in game mode anyway. So we assume that if we are in a job, we are in game mode.
if (!assumeInPlayMode && !Unity.Jobs.LowLevel.Unsafe.JobsUtility.IsExecutingJob && !Application.isPlaying) {
BlockUntilCalculated(path);
}
}
/// <summary>
/// Cached NNConstraint.None to avoid unnecessary allocations.
/// This should ideally be fixed by making NNConstraint an immutable class/struct.
/// </summary>
static readonly NNConstraint NNConstraintNone = NNConstraint.None;
/// <summary>
/// Returns the nearest node to a position.
/// This method will search through all graphs and query them for the closest node to this position, and then it will return the closest one of those.
///
/// Equivalent to GetNearest(position, NNConstraint.None).
///
/// <code>
/// // Find the closest node to this GameObject's position
/// GraphNode node = AstarPath.active.GetNearest(transform.position).node;
///
/// if (node.Walkable) {
/// // Yay, the node is walkable, we can place a tower here or something
/// }
/// </code>
///
/// See: Pathfinding.NNConstraint
/// </summary>
public NNInfo GetNearest (Vector3 position) {
return GetNearest(position, null);
}
/// <summary>
/// Returns the nearest node to a point using the specified NNConstraint.
///
/// Searches through all graphs for their nearest nodes to the specified position and picks the closest one.
/// The NNConstraint can be used to specify constraints on which nodes can be chosen such as only picking walkable nodes.
///
/// <code>
/// GraphNode node = AstarPath.active.GetNearest(transform.position, NNConstraint.Walkable).node;
/// </code>
///
/// <code>
/// var constraint = NNConstraint.None;
///
/// // Constrain the search to walkable nodes only
/// constraint.constrainWalkability = true;
/// constraint.walkable = true;
///
/// // Constrain the search to only nodes with tag 3 or tag 5
/// // The 'tags' field is a bitmask
/// constraint.constrainTags = true;
/// constraint.tags = (1 << 3) | (1 << 5);
///
/// var info = AstarPath.active.GetNearest(transform.position, constraint);
/// var node = info.node;
/// var closestPoint = info.position;
/// </code>
///
/// See: <see cref="NNConstraint"/>
/// </summary>
/// <param name="position">The point to find nodes close to</param>
/// <param name="constraint">The constraint which determines which graphs and nodes are acceptable to search on. May be null, in which case all nodes will be considered acceptable.</param>
public NNInfo GetNearest (Vector3 position, NNConstraint constraint) {
// Cache property lookups
var graphs = this.graphs;
var maxNearestNodeDistanceSqr = constraint == null || constraint.constrainDistance ? this.maxNearestNodeDistanceSqr : float.PositiveInfinity;
NNInfo nearestNode = NNInfo.Empty;
if (graphs == null || graphs.Length == 0) return nearestNode;
// Use a fast path in case there is only one graph.
// This improves performance by about 10% when there is only one graph.
if (graphs.Length == 1) {
var graph = graphs[0];
if (graph == null || (constraint != null && !constraint.SuitableGraph(0, graph))) {
return nearestNode;
}
nearestNode = graph.GetNearest(position, constraint, maxNearestNodeDistanceSqr);
UnityEngine.Assertions.Assert.IsTrue(nearestNode.node == null || nearestNode.distanceCostSqr <= maxNearestNodeDistanceSqr);
} else {
UnsafeSpan<(float, int)> distances;
unsafe {
// The number of graphs is limited to GraphNode.MaxGraphIndex (256),
// and typically there are only a few graphs, so allocating this on the stack is fine.
var distancesPtr = stackalloc (float, int)[graphs.Length];
distances = new UnsafeSpan<(float, int)>(distancesPtr, graphs.Length);
}
// Iterate through all graphs and find a lower bound on the distance to the nearest node.
// We then sort these distances and run the full get nearest search on the graphs in order of increasing distance.
// This is an optimization to avoid running the full get nearest search on graphs which are far away.
int numCandidateGraphs = 0;
for (int i = 0; i < graphs.Length; i++) {
NavGraph graph = graphs[i];
// Check if this graph should be searched
if (graph == null || (constraint != null && !constraint.SuitableGraph(i, graph))) {
continue;
}
var lowerBound = graph.NearestNodeDistanceSqrLowerBound(position, constraint);
if (lowerBound > maxNearestNodeDistanceSqr) continue;
distances[numCandidateGraphs++] = (lowerBound, i);
}
distances = distances.Slice(0, numCandidateGraphs);
distances.Sort();
for (int i = 0; i < distances.Length; i++) {
if (distances[i].Item1 > maxNearestNodeDistanceSqr) break;
var graph = graphs[distances[i].Item2];
NNInfo nnInfo = graph.GetNearest(position, constraint, maxNearestNodeDistanceSqr);
if (nnInfo.distanceCostSqr < maxNearestNodeDistanceSqr) {
maxNearestNodeDistanceSqr = nnInfo.distanceCostSqr;
nearestNode = nnInfo;
}
}
}
return nearestNode;
}
/// <summary>
/// Returns the node closest to the ray (slow).
/// Warning: This function is brute-force and very slow, use with caution
/// </summary>
public GraphNode GetNearest (Ray ray) {
if (graphs == null) return null;
float minDist = Mathf.Infinity;
GraphNode nearestNode = null;
Vector3 lineDirection = ray.direction;
Vector3 lineOrigin = ray.origin;
for (int i = 0; i < graphs.Length; i++) {
NavGraph graph = graphs[i];
graph.GetNodes(node => {
Vector3 pos = (Vector3)node.position;
Vector3 p = lineOrigin+(Vector3.Dot(pos-lineOrigin, lineDirection)*lineDirection);
float tmp = Mathf.Abs(p.x-pos.x);
tmp *= tmp;
if (tmp > minDist) return;
tmp = Mathf.Abs(p.z-pos.z);
tmp *= tmp;
if (tmp > minDist) return;
float dist = (p-pos).sqrMagnitude;
if (dist < minDist) {
minDist = dist;
nearestNode = node;
}
});
}
return nearestNode;
}
/// <summary>
/// Captures a snapshot of a part of the graphs, to allow restoring it later.
///
/// This is useful if you want to do a graph update, but you want to be able to restore the graph to the previous state.
///
/// The snapshot will capture enough information to restore the graphs, assuming the world only changed within the given bounding box.
/// This means the captured region may be larger than the bounding box.
///
/// <b>Limitations:</b>
/// - Currently, the <see cref="GridGraph"/> and <see cref="LayerGridGraph"/> supports snapshots. Other graph types do not support it.
/// - The graph must not change its dimensions or other core parameters between the time the snapshot is taken and the time it is restored.
/// - Custom node connections may not be preserved. Unless they are added as off-mesh links using e.g. a <see cref="NodeLink2"/> component.
/// - The snapshot must not be captured during a work item, graph update or when the graphs are being scanned, as the graphs may not be in a consistent state during those times.
///
/// See: <see cref="GraphUpdateUtilities.UpdateGraphsNoBlock"/>, which uses this method internally.
/// See: <see cref="NavGraph.Snapshot"/>
///
/// Note: You must dispose the returned snapshot when you are done with it, to avoid leaking memory.
/// </summary>
public GraphSnapshot Snapshot (Bounds bounds, GraphMask graphMask) {
Profiler.BeginSample("Capturing Graph Snapshot");
var inner = new List<IGraphSnapshot>();
for (int i = 0; i < graphs.Length; i++) {
if (graphs[i] != null && graphMask.Contains(i)) {
var s = graphs[i].Snapshot(bounds);
if (s != null) inner.Add(s);
}
}
Profiler.EndSample();
return new GraphSnapshot(inner);
}
/// <summary>
/// Allows you to access read-only graph data in jobs safely.
///
/// You can for example use AstarPath.active.GetNearest(...) in a job.
///
/// Using <see cref="AstarPath.StartPath"/> is always safe to use in jobs even without calling this method.
///
/// When a graph update, work item, or graph scan would start, it will first block on the given dependency
/// to ensure no race conditions occur.
///
/// If you do not call this method, then a graph update might start in the middle of your job, causing race conditions
/// and all manner of other hard-to-diagnose bugs.
///
/// <code>
/// var readLock = AstarPath.active.LockGraphDataForReading();
/// var handle = new MyJob {
/// // ...
/// }.Schedule(readLock.dependency);
/// readLock.UnlockAfter(handle);
/// </code>
///
/// See: <see cref="LockGraphDataForWriting"/>
/// See: <see cref="graphDataLock"/>
/// </summary>
public RWLock.ReadLockAsync LockGraphDataForReading() => graphDataLock.Read();
/// <summary>
/// Aquires an exclusive lock on the graph data asynchronously.
/// This is used when graphs want to modify graph data.
///
/// This is a low-level primitive, usually you do not need to use this method.
///
/// <code>
/// var readLock = AstarPath.active.LockGraphDataForReading();
/// var handle = new MyJob {
/// // ...
/// }.Schedule(readLock.dependency);
/// readLock.UnlockAfter(handle);
/// </code>
///
/// See: <see cref="LockGraphDataForReading"/>
/// See: <see cref="graphDataLock"/>
/// </summary>
public RWLock.WriteLockAsync LockGraphDataForWriting() => graphDataLock.Write();
/// <summary>
/// Aquires an exclusive lock on the graph data.
/// This is used when graphs want to modify graph data.
///
/// This is a low-level primitive, usually you do not need to use this method.
///
/// <code>
/// var readLock = AstarPath.active.LockGraphDataForReading();
/// var handle = new MyJob {
/// // ...
/// }.Schedule(readLock.dependency);
/// readLock.UnlockAfter(handle);
/// </code>
///
/// See: <see cref="LockGraphDataForReading"/>
/// See: <see cref="graphDataLock"/>
/// </summary>
public RWLock.LockSync LockGraphDataForWritingSync() => graphDataLock.WriteSync();
/// <summary>
/// Obstacle data for navmesh edges.
///
/// This can be used to get information about the edge/borders of the navmesh.
/// It can also be queried in burst jobs. Just make sure you release the read lock after you are done with it.
///
/// Note: This is not a method that you are likely to need to use.
/// It is used internally for things like local avoidance.
/// </summary>
public NavmeshEdges.NavmeshBorderData GetNavmeshBorderData(out RWLock.CombinedReadLockAsync readLock) => hierarchicalGraph.navmeshEdges.GetNavmeshEdgeData(out readLock);
}
|