Skip to content

Commit d788086

Browse files
authored
Merge pull request #159 from cortex-command-community/multithreading-ai
Multithreaded AI
2 parents bcbe1f7 + 436c9d1 commit d788086

File tree

9 files changed

+367
-192
lines changed

9 files changed

+367
-192
lines changed

Data/Base.rte/AI/CrabBehaviors.lua

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,12 @@ function CrabBehaviors.Sentry(AI, Owner, Abort)
100100

101101
Owner:AddAISceneWaypoint(Vector(Owner.Pos.X, 0));
102102
Owner:UpdateMovePath();
103-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
104-
if _abrt then return true end
103+
104+
-- wait until movepath is updated
105+
while Owner.IsWaitingOnNewMovePath do
106+
local _ai, _ownr, _abrt = coroutine.yield();
107+
if _abrt then return true end
108+
end
105109

106110
-- face the direction of the first waypoint
107111
for WptPos in Owner.MovePath do
@@ -372,13 +376,19 @@ function CrabBehaviors.GoToWpt(AI, Owner, Abort)
372376
end
373377
end
374378
end
375-
else -- no waypoint list, create one in several small steps to reduce lag
379+
else -- no waypoint list, create one
376380
local TmpList = {};
377381
table.insert(TmpList, {Pos=Owner.Pos});
382+
378383
Owner:UpdateMovePath();
384+
385+
-- wait until movepath is updated
386+
while Owner.IsWaitingOnNewMovePath do
387+
local _ai, _ownr, _abrt = coroutine.yield();
388+
if _abrt then return true end
389+
end
390+
379391
Owner:DrawWaypoints(true);
380-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
381-
if _abrt then return true end
382392

383393
for WptPos in Owner.MovePath do -- skip any waypoint too close to the previous one
384394
if SceneMan:ShortestDistance(TmpList[#TmpList].Pos, WptPos, false):MagnitudeIsGreaterThan(10) then
@@ -399,9 +409,6 @@ function CrabBehaviors.GoToWpt(AI, Owner, Abort)
399409
end
400410
end
401411

402-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
403-
if _abrt then return true end
404-
405412
WptList = TmpList;
406413

407414
-- create the move path seen on the screen

Data/Base.rte/AI/HumanBehaviors.lua

Lines changed: 94 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,12 @@ function HumanBehaviors.Sentry(AI, Owner, Abort)
320320
Owner:ClearMovePath();
321321
Owner:AddAISceneWaypoint(Vector(Owner.Pos.X, 0));
322322
Owner:UpdateMovePath();
323-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
324-
if _abrt then return true end
323+
324+
-- wait until movepath is updated
325+
while Owner.IsWaitingOnNewMovePath do
326+
local _ai, _ownr, _abrt = coroutine.yield();
327+
if _abrt then return true end
328+
end
325329

326330
-- face the direction of the first waypoint
327331
for WptPos in Owner.MovePath do
@@ -528,12 +532,14 @@ function HumanBehaviors.Patrol(AI, Owner, Abort)
528532

529533
if Dist:MagnitudeIsGreaterThan(20) then
530534
Owner:ClearAIWaypoints();
531-
Owner:AddAISceneWaypoint(Free);
532-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
533-
if _abrt then return true end
535+
Owner:AddAISceneWaypoint(Free);
534536
Owner:UpdateMovePath();
535-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
536-
if _abrt then return true end
537+
538+
-- wait until movepath is updated
539+
while Owner.IsWaitingOnNewMovePath do
540+
local _ai, _ownr, _abrt = coroutine.yield();
541+
if _abrt then return true end
542+
end
537543

538544
local PrevPos = Vector(Owner.Pos.X, Owner.Pos.Y);
539545
for WptPos in Owner.MovePath do
@@ -553,11 +559,13 @@ function HumanBehaviors.Patrol(AI, Owner, Abort)
553559
if Dist:MagnitudeIsGreaterThan(20) then
554560
Owner:ClearAIWaypoints();
555561
Owner:AddAISceneWaypoint(Free);
556-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
557-
if _abrt then return true end
558562
Owner:UpdateMovePath();
559-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
560-
if _abrt then return true end
563+
564+
-- wait until movepath is updated
565+
while Owner.IsWaitingOnNewMovePath do
566+
local _ai, _ownr, _abrt = coroutine.yield();
567+
if _abrt then return true end
568+
end
561569

562570
local PrevPos = Vector(Owner.Pos.X, Owner.Pos.Y);
563571
for WptPos in Owner.MovePath do
@@ -843,6 +851,12 @@ function HumanBehaviors.BrainSearch(AI, Owner, Abort)
843851
Owner:AddAISceneWaypoint(Act.Pos);
844852
Owner:UpdateMovePath();
845853

854+
-- wait until movepath is updated
855+
while Owner.IsWaitingOnNewMovePath do
856+
local _ai, _ownr, _abrt = coroutine.yield();
857+
if _abrt then return true end
858+
end
859+
846860
local OldWpt, deltaY;
847861
local index = 0;
848862
local height = 0;
@@ -951,55 +965,46 @@ function HumanBehaviors.WeaponSearch(AI, Owner, Abort)
951965
table.sort(devices, function(device, otherDevice) return device.distance.SqrMagnitude < otherDevice.distance.SqrMagnitude end);
952966

953967
if #devices > 0 then
954-
local _ai, _ownr, _abrt = coroutine.yield();
955-
if _abrt then return true end
956-
957-
local maxWaypointDistance = 36;
968+
local maxPathLength = 36; --TODO when this gets turned into a part of pathing calc, use it that way instead
958969
if AI.isPlayerOwned then
959-
maxWaypointDistance = 10;
970+
maxPathLength = 10;
960971
end
961-
972+
973+
local searchesRemaining = #devices;
962974
local devicesToPickUp = {};
963975
for _, deviceEntry in pairs(devices) do
964976
local device = deviceEntry.device;
965977
if MovableMan:ValidMO(device) then
966-
local pathToItemIsObstructed = false;
967-
if AI.useExpensiveToolAndWeaponSearch then
968-
pathToItemIsObstructed = SceneMan:CastStrengthRay(Owner.Pos, deviceEntry.distance, 5, Vector(), 4, rte.grassID, true);
969-
end
970-
local pathfinderNodeSize = 20; -- TODO this should be read from cpp
971-
972-
local distanceToTarget = pathToItemIsObstructed and SceneMan.Scene:CalculatePath(Owner.Pos, device.Pos, false, 1, Owner.Team) or (deviceEntry.distance.Magnitude / pathfinderNodeSize);
973-
if distanceToTarget < maxWaypointDistance and distanceToTarget > -1 then
974-
local score = distanceToTarget
975-
if device:HasObjectInGroup("Weapons - Primary") or device:HasObjectInGroup("Weapons - Heavy") then
976-
score = distanceToTarget * 0.4; -- prioritize primary or heavy weapons
977-
elseif device.ClassName == "TDExplosive" then
978-
score = distanceToTarget * 1.4; -- avoid grenades if there are other weapons
979-
elseif device:IsTool() then
980-
if pickupDiggers and device:HasObjectInGroup("Tools - Diggers") then
981-
score = distanceToTarget * 1.8; -- avoid diggers if there are other weapons
982-
else
983-
distanceToTarget = maxWaypointDistance;
984-
end
985-
end
986-
987-
if distanceToTarget < maxWaypointDistance then
988-
table.insert(devicesToPickUp, {device = device, score = score});
989-
if not pathToItemIsObstructed or score < 1 then
990-
local _ai, _ownr, _abrt = coroutine.yield();
991-
if _abrt then return true end
992-
break;
978+
SceneMan.Scene:CalculatePathAsync(
979+
function(pathRequest)
980+
local pathLength = pathRequest.PathLength;
981+
if pathRequest.Status ~= PathRequest.NoSolution and pathLength < maxPathLength then
982+
local score = pathLength;
983+
if device:HasObjectInGroup("Weapons - Primary") or device:HasObjectInGroup("Weapons - Heavy") then
984+
score = pathLength * 0.4; -- prioritize primary or heavy weapons
985+
elseif device.ClassName == "TDExplosive" then
986+
score = pathLength * 1.4; -- avoid grenades if there are other weapons
987+
elseif device:IsTool() then
988+
if pickupDiggers and device:HasObjectInGroup("Tools - Diggers") then
989+
score = pathLength * 1.8; -- avoid diggers if there are other weapons
990+
else
991+
pathLength = maxPathLength;
992+
end
993+
end
994+
if pathLength < maxPathLength then
995+
table.insert(devicesToPickUp, {device = device, score = score});
996+
end
993997
end
998+
searchesRemaining = searchesRemaining - 1;
994999
end
995-
for i = 1, 2 do
996-
local _ai, _ownr, _abrt = coroutine.yield();
997-
if _abrt then return true end
998-
end
999-
end
1000+
, Owner.Pos, device.Pos, false, Owner.DigStrength, Owner.Team);
10001001
end
10011002
end
1002-
1003+
1004+
while searchesRemaining > 0 do
1005+
local _ai, _ownr, _abrt = coroutine.yield();
1006+
if _abrt then return true end
1007+
end
10031008

10041009
AI.PickupHD = nil;
10051010
table.sort(devicesToPickUp, function(A,B) return A.score < B.score end);
@@ -1033,6 +1038,13 @@ function HumanBehaviors.WeaponSearch(AI, Owner, Abort)
10331038
end
10341039

10351040
Owner:UpdateMovePath();
1041+
1042+
-- wait until movepath is updated
1043+
while Owner.IsWaitingOnNewMovePath do
1044+
local _ai, _ownr, _abrt = coroutine.yield();
1045+
if _abrt then return true end
1046+
end
1047+
10361048
AI:CreateGoToBehavior(Owner);
10371049
end
10381050
end
@@ -1081,41 +1093,34 @@ function HumanBehaviors.ToolSearch(AI, Owner, Abort)
10811093
table.sort(devices, function(device, otherDevice) return device.distance.SqrMagnitude < otherDevice.distance.SqrMagnitude end);
10821094

10831095
if #devices > 0 then
1084-
local _ai, _ownr, _abrt = coroutine.yield();
1085-
if _abrt then return true end
1086-
1087-
local maxWaypointDistance = 16;
1096+
local maxPathLength = 16;
10881097
if Owner.AIMode == Actor.AIMODE_GOLDDIG then
1089-
maxWaypointDistance = 30;
1098+
maxPathLength = 30;
10901099
elseif AI.isPlayerOwned then
1091-
maxWaypointDistance = 5;
1100+
maxPathLength = 5;
10921101
end
1093-
1102+
1103+
local searchesRemaining = #devices;
10941104
local devicesToPickUp = {};
10951105
for _, deviceEntry in pairs(devices) do
10961106
local device = deviceEntry.device;
10971107
if MovableMan:ValidMO(device) then
1098-
local pathToItemIsObstructed = false;
1099-
if AI.useExpensiveToolAndWeaponSearch then
1100-
pathToItemIsObstructed = SceneMan:CastStrengthRay(Owner.Pos, deviceEntry.distance, 5, Vector(), 4, rte.grassID, true);
1101-
end
1102-
local pathfinderNodeSize = 20; -- TODO this should be read from cpp
1103-
1104-
local distanceToTarget = pathToItemIsObstructed and SceneMan.Scene:CalculatePath(Owner.Pos, device.Pos, false, 1, Owner.Team) or (deviceEntry.distance.Magnitude / pathfinderNodeSize);
1105-
if distanceToTarget < maxWaypointDistance and distanceToTarget > -1 then
1106-
table.insert(devicesToPickUp, {device = device, score = distanceToTarget});
1107-
1108-
if not pathToItemIsObstructed or distanceToTarget < 1 then
1109-
break;
1110-
end
1111-
1112-
for i = 1, 2 do
1113-
local _ai, _ownr, _abrt = coroutine.yield();
1114-
if _abrt then return true end
1108+
SceneMan.Scene:CalculatePathAsync(
1109+
function(pathRequest)
1110+
local pathLength = pathRequest.PathLength;
1111+
if pathRequest.Status ~= PathRequest.NoSolution and pathLength < maxPathLength then
1112+
table.insert(devicesToPickUp, {device = device, score = pathLength});
1113+
end
1114+
searchesRemaining = searchesRemaining - 1;
11151115
end
1116-
end
1116+
, Owner.Pos, device.Pos, false, Owner.DigStrength, Owner.Team);
11171117
end
11181118
end
1119+
1120+
while searchesRemaining > 0 do
1121+
local _ai, _ownr, _abrt = coroutine.yield();
1122+
if _abrt then return true end
1123+
end
11191124

11201125
AI.PickupHD = nil;
11211126
table.sort(devicesToPickUp, function(A,B) return A.score < B.score end); -- sort the items in order of discounted distance
@@ -1151,6 +1156,13 @@ function HumanBehaviors.ToolSearch(AI, Owner, Abort)
11511156
end
11521157

11531158
Owner:UpdateMovePath();
1159+
1160+
-- wait until movepath is updated
1161+
while Owner.IsWaitingOnNewMovePath do
1162+
local _ai, _ownr, _abrt = coroutine.yield();
1163+
if _abrt then return true end
1164+
end
1165+
11541166
AI:CreateGoToBehavior(Owner);
11551167
end
11561168
end
@@ -1909,6 +1921,12 @@ function HumanBehaviors.GoToWpt(AI, Owner, Abort)
19091921
else
19101922
Owner:UpdateMovePath();
19111923

1924+
-- wait until movepath is updated
1925+
while Owner.IsWaitingOnNewMovePath do
1926+
local _ai, _ownr, _abrt = coroutine.yield();
1927+
if _abrt then return true end
1928+
end
1929+
19121930
-- have we arrived?
19131931
if not Owner.MOMoveTarget then
19141932
local ProxyWpt = SceneMan:MovePointToGround(Owner:GetLastAIWaypoint(), Owner.Height*0.2, 5);
@@ -1927,7 +1945,7 @@ function HumanBehaviors.GoToWpt(AI, Owner, Abort)
19271945
end
19281946
end
19291947

1930-
-- no waypoint list, create one in several small steps to reduce lag
1948+
-- no waypoint list, create one
19311949
local PathDump = {};
19321950
if Owner.MOMoveTarget and MovableMan:ValidMO(Owner.MOMoveTarget) then
19331951
Owner:DrawWaypoints(false);
@@ -1941,18 +1959,12 @@ function HumanBehaviors.GoToWpt(AI, Owner, Abort)
19411959
Owner:ClearMovePath();
19421960
Owner:AddToMovePathEnd(Owner.MOMoveTarget.Pos);
19431961
else
1944-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
1945-
if _abrt then return true end
1946-
19471962
-- copy the MovePath to a temporary table so we can yield safely while working on the path
19481963
for WptPos in Owner.MovePath do
19491964
table.insert(PathDump, WptPos);
19501965
end
19511966
end
19521967

1953-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
1954-
if _abrt then return true end
1955-
19561968
-- copy useful waypoints to a temporary path
19571969
local TmpWpts = {};
19581970
table.insert(TmpWpts, {Pos=Owner.Pos});
@@ -1971,8 +1983,6 @@ function HumanBehaviors.GoToWpt(AI, Owner, Abort)
19711983
end
19721984

19731985
LastPos = WptPos;
1974-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
1975-
if _abrt then return true end
19761986
end
19771987

19781988
-- No path
@@ -2006,9 +2016,6 @@ function HumanBehaviors.GoToWpt(AI, Owner, Abort)
20062016
end
20072017
end
20082018

2009-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
2010-
if _abrt then return true end
2011-
20122019
table.sort(GapList, function(A, B) return A.score > B.score end); -- sort largest first
20132020

20142021
for _, LZ in pairs(GapList) do
@@ -2030,9 +2037,6 @@ function HumanBehaviors.GoToWpt(AI, Owner, Abort)
20302037
break;
20312038
end
20322039
end
2033-
2034-
local _ai, _ownr, _abrt = coroutine.yield(); -- wait until next frame
2035-
if _abrt then return true end
20362040
end
20372041
end
20382042
end

Data/Base.rte/AI/NativeHumanAI.lua

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ NativeHumanAI = {};
44

55
function NativeHumanAI:Create(Owner)
66
local Members = {};
7-
8-
Members.useExpensiveToolAndWeaponSearch = SceneMan.SceneWidth * SceneMan.SceneHeight < 10000000;
97

108
Members.lateralMoveState = Actor.LAT_STILL;
119
Members.proneState = AHuman.NOTPRONE;

Data/Base.rte/AI/PathFinder.lua

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,25 @@ function Create(self)
99
end
1010

1111
function Update(self)
12+
if self.IsWaitingOnNewMovePath then
13+
return;
14+
end
15+
16+
if #gWayPoints == 0 and #self.MovePath ~= 0 then
17+
for Wpt in self.MovePath do
18+
table.insert(gWaypoints, Vector(Wpt.X, Wpt.Y));
19+
end
20+
end
21+
1222
if gFindPathFrom and gFindPathTo then
1323
self.Pos = gFindPathFrom;
1424
gFindPathFrom = nil;
1525

1626
self:ClearAIWaypoints();
1727
self:AddAISceneWaypoint(gFindPathTo);
1828
self:UpdateMovePath();
19-
gFindPathTo = nil;
2029

30+
gFindPathTo = nil;
2131
gWaypoints = {};
22-
for Wpt in self.MovePath do
23-
table.insert(gWaypoints, Vector(Wpt.X, Wpt.Y));
24-
end
2532
end
2633
end

0 commit comments

Comments
 (0)