diff --git a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Runners/AgentTypeJobRunner.cs b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Runners/AgentTypeJobRunner.cs index 921a1084..6c6feaee 100644 --- a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Runners/AgentTypeJobRunner.cs +++ b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Runners/AgentTypeJobRunner.cs @@ -13,16 +13,16 @@ namespace CrashKonijn.Goap.Runtime public class AgentTypeJobRunner : IAgentTypeJobRunner { private readonly IAgentType agentType; - private readonly IGraphResolver resolver; - - private List resolveHandles = new(); - private readonly IExecutableBuilder executableBuilder; + private readonly IConditionBuilder conditionBuilder; + private readonly ICostBuilder costBuilder; private readonly IEnabledBuilder enabledBuilder; + private readonly IExecutableBuilder executableBuilder; private readonly IPositionBuilder positionBuilder; - private readonly ICostBuilder costBuilder; - private readonly IConditionBuilder conditionBuilder; + private readonly IGraphResolver resolver; + + private readonly List goalIndexes = new(); - private List goalIndexes = new(); + private readonly List resolveHandles = new(); public AgentTypeJobRunner(IAgentType agentType, IGraphResolver graphResolver) { @@ -51,6 +51,60 @@ public void Run(IMonoGoapActionProvider[] queue) } } + public void Complete() + { + foreach (var resolveHandle in this.resolveHandles) + { + var result = resolveHandle.Handle.Complete(); + + if (resolveHandle.ActionProvider.GoalRequest != resolveHandle.GoalRequest) + continue; + + if (resolveHandle.ActionProvider.IsNull()) + continue; + + var goal = result.Goal; + if (goal == null) + { + resolveHandle.ActionProvider.Events.NoActionFound(resolveHandle.GoalRequest); + continue; + } + + var action = result.Actions.FirstOrDefault() as IGoapAction; + + if (action is null) + { + resolveHandle.ActionProvider.Events.NoActionFound(resolveHandle.GoalRequest); + continue; + } + + if (action != resolveHandle.ActionProvider.Receiver.ActionState.Action) + resolveHandle.ActionProvider.SetAction(new GoalResult + { + Goal = goal, + Plan = result.Actions, + Action = action + }); + } + + this.resolveHandles.Clear(); + } + + public void Dispose() + { + foreach (var resolveHandle in this.resolveHandles) + { + resolveHandle.Handle.Complete(); + } + + this.resolver.Dispose(); + } + + public IGraph GetGraph() + { + return this.resolver.GetGraph(); + } + private void Run(IMonoGoapActionProvider actionProvider) { if (actionProvider.IsNull()) @@ -101,8 +155,8 @@ private void Run(IMonoGoapActionProvider actionProvider) Positions = new NativeArray(this.positionBuilder.Build(), Allocator.TempJob), Costs = new NativeArray(this.costBuilder.Build(), Allocator.TempJob), ConditionsMet = new NativeArray(this.conditionBuilder.Build(), Allocator.TempJob), - DistanceMultiplier = actionProvider.DistanceMultiplier, - }), + DistanceMultiplier = actionProvider.DistanceMultiplier + }) }); } @@ -144,8 +198,12 @@ private void FillBuilders(IMonoGoapActionProvider actionProvider) var target = actionProvider.WorldData.GetTarget(node); this.executableBuilder.SetExecutable(node, node.IsExecutable(actionProvider.Receiver, allMet)); - this.enabledBuilder.SetEnabled(node, node.IsEnabled(actionProvider.Receiver)); - this.costBuilder.SetCost(node, node.GetCost(actionProvider.Receiver, actionProvider.Receiver.Injector, target)); + + var isEnabled = node.IsEnabled(actionProvider.Receiver); + var cost = isEnabled ? node.GetCost(actionProvider.Receiver, actionProvider.Receiver.Injector, target) : 0f; + + this.enabledBuilder.SetEnabled(node, isEnabled); + this.costBuilder.SetCost(node, cost); this.positionBuilder.SetPosition(node, target?.GetValidPosition()); } @@ -192,47 +250,6 @@ private bool MayResolve(IGoapActionProvider actionProvider) return actionProvider.Receiver.ActionState.RunState.MayResolve(agent); } - public void Complete() - { - foreach (var resolveHandle in this.resolveHandles) - { - var result = resolveHandle.Handle.Complete(); - - if (resolveHandle.ActionProvider.GoalRequest != resolveHandle.GoalRequest) - continue; - - if (resolveHandle.ActionProvider.IsNull()) - continue; - - var goal = result.Goal; - if (goal == null) - { - resolveHandle.ActionProvider.Events.NoActionFound(resolveHandle.GoalRequest); - continue; - } - - var action = result.Actions.FirstOrDefault() as IGoapAction; - - if (action is null) - { - resolveHandle.ActionProvider.Events.NoActionFound(resolveHandle.GoalRequest); - continue; - } - - if (action != resolveHandle.ActionProvider.Receiver.ActionState.Action) - { - resolveHandle.ActionProvider.SetAction(new GoalResult - { - Goal = goal, - Plan = result.Actions, - Action = action, - }); - } - } - - this.resolveHandles.Clear(); - } - private void LogRequest(IGoapActionProvider actionProvider, IGoalRequest request) { #if UNITY_EDITOR @@ -255,29 +272,17 @@ private void LogRequest(IGoapActionProvider actionProvider, IGoalRequest request #endif } - public void Dispose() - { - foreach (var resolveHandle in this.resolveHandles) - { - resolveHandle.Handle.Complete(); - } - - this.resolver.Dispose(); - } - private class JobRunHandle { - public IMonoGoapActionProvider ActionProvider { get; } - public IResolveHandle Handle { get; set; } - public IGoalRequest GoalRequest { get; set; } - public JobRunHandle(IMonoGoapActionProvider actionProvider, IGoalRequest goalRequest) { this.ActionProvider = actionProvider; this.GoalRequest = goalRequest; } - } - public IGraph GetGraph() => this.resolver.GetGraph(); + public IMonoGoapActionProvider ActionProvider { get; } + public IResolveHandle Handle { get; set; } + public IGoalRequest GoalRequest { get; } + } } -} +} \ No newline at end of file diff --git a/Package/Tests/CrashKonijn.Goap.Tests/UnitTests/AgentTypeJobRunnerTests.cs b/Package/Tests/CrashKonijn.Goap.Tests/UnitTests/AgentTypeJobRunnerTests.cs index db503cc5..8c5f632a 100644 --- a/Package/Tests/CrashKonijn.Goap.Tests/UnitTests/AgentTypeJobRunnerTests.cs +++ b/Package/Tests/CrashKonijn.Goap.Tests/UnitTests/AgentTypeJobRunnerTests.cs @@ -15,9 +15,9 @@ namespace CrashKonijn.Goap.UnitTests { public class AgentTypeJobRunnerTests { - private IMonoGoapActionProvider goapActionProvider; private IAgent agent; private IAgentType agentType; + private IMonoGoapActionProvider goapActionProvider; [SetUp] public void Setup() @@ -32,7 +32,7 @@ public void Setup() this.agentType = Substitute.For(); this.goapActionProvider.AgentType.Returns(this.agentType); - + // Unity sometimes thinks that a temporary job is leaking memory // This is not the case, so we ignore the message // This can trigger in any test, even the ones that don't use the Job system @@ -128,7 +128,7 @@ public void Run_AgentHasNoCurrentGoalRequest_DoesNotRun() var sensorRunner = Substitute.For(); this.agentType.SensorRunner.Returns(sensorRunner); - this.agentType.GetAllNodes().Returns(new List { }); + this.agentType.GetAllNodes().Returns(new List()); this.agentType.GoapConfig.Returns(GoapConfig.Default); var resolver = Substitute.For(); @@ -166,7 +166,7 @@ public void Run_AgentHasCurrentGoalAndNoAction_Runs() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal }); + this.agentType.GetGoals().Returns(new List { goal }); var resolver = Substitute.For(); @@ -202,7 +202,7 @@ public void Run_AgentHasCurrentGoalAndNoAction_RunsWithMultipleGoals() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal1, goal2 }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal1, goal2 }); + this.agentType.GetGoals().Returns(new List { goal1, goal2 }); var resolver = Substitute.For(); @@ -239,7 +239,7 @@ public void Run_AgentHasCurrentGoalAndDoesHaveAction_Runs() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal }); + this.agentType.GetGoals().Returns(new List { goal }); var resolver = Substitute.For(); @@ -276,7 +276,7 @@ public void Run_AgentHasCompletedGoal_CallsGoalCompleteEvent() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal }); + this.agentType.GetGoals().Returns(new List { goal }); this.agentType.GoapConfig.ConditionObserver.IsMet(Arg.Any()).Returns(true); @@ -316,7 +316,7 @@ public void Run_AgentHasNotCompletedGoal_DoesntCallGoalCompleteEvent() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal }); + this.agentType.GetGoals().Returns(new List { goal }); this.agentType.GoapConfig.ConditionObserver.IsMet(Arg.Any()).Returns(false); @@ -358,13 +358,13 @@ public void Run_AgentHasCurrentGoalAndNoAction_SetsTheActionOnAgent() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal }); + this.agentType.GetGoals().Returns(new List { goal }); var handle = Substitute.For(); handle.Complete().Returns(new JobResult { Goal = goal, - Actions = new IConnectable[] { action }, + Actions = new IConnectable[] { action } }); var resolver = Substitute.For(); @@ -404,13 +404,13 @@ public void Run_AgentHasCurrentGoalAndAction_ResolvingSameActionDoesntCallSet() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal }); + this.agentType.GetGoals().Returns(new List { goal }); var handle = Substitute.For(); handle.Complete().Returns(new JobResult { Goal = goal, - Actions = new IConnectable[] { action }, + Actions = new IConnectable[] { action } }); var resolver = Substitute.For(); @@ -439,7 +439,7 @@ public void Run_WithMultipleGoals_DoesntResolveCompletedGoal() goal2.Conditions.Returns(new[] { condition2 }); var goalRequest = Substitute.For(); - goalRequest.Goals.Returns(new List() { goal1, goal2 }); + goalRequest.Goals.Returns(new List { goal1, goal2 }); this.goapActionProvider.GoalRequest.Returns(goalRequest); this.goapActionProvider.Receiver.ActionState.Action.ReturnsNull(); @@ -453,7 +453,7 @@ public void Run_WithMultipleGoals_DoesntResolveCompletedGoal() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal1, goal2 }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal1, goal2 }); + this.agentType.GetGoals().Returns(new List { goal1, goal2 }); this.agentType.GoapConfig.Returns(goapConfig); var resolver = Substitute.For(); @@ -480,7 +480,7 @@ public void Run_AgentWithMisMatchingAgentTypes_DoesntRun() goalResult.Goal.Returns(goal); var request = Substitute.For(); - request.Goals.Returns(new List() { goal }); + request.Goals.Returns(new List { goal }); this.goapActionProvider.CurrentPlan.Returns(goalResult); this.goapActionProvider.GoalRequest.Returns(request); @@ -491,7 +491,7 @@ public void Run_AgentWithMisMatchingAgentTypes_DoesntRun() this.agentType.SensorRunner.Returns(sensorRunner); this.agentType.GetAllNodes().Returns(new List { goal }); this.agentType.GetActions().Returns(new List()); - this.agentType.GetGoals().Returns(new List() { goal }); + this.agentType.GetGoals().Returns(new List { goal }); var otherAgentType = Substitute.For(); this.goapActionProvider.AgentType.Returns(otherAgentType); @@ -508,5 +508,83 @@ public void Run_AgentWithMisMatchingAgentTypes_DoesntRun() sensorRunner.Received(0).SenseLocal(this.goapActionProvider, Arg.Any()); resolver.Received(0).StartResolve(Arg.Any()); } + + [Test] + public void Run_EnabledAction_CallsGetCost() + { + // Arrange + var goal = Substitute.For(); + goal.Conditions.Returns(new[] { Substitute.For() }); + + var goalResult = Substitute.For(); + goalResult.Goal.Returns(goal); + + var request = Substitute.For(); + request.Goals.Returns(new List { goal }); + + var action = Substitute.For(); + action.IsEnabled(Arg.Any()).Returns(true); + + this.goapActionProvider.CurrentPlan.Returns(goalResult); + this.goapActionProvider.GoalRequest.Returns(request); + this.goapActionProvider.Receiver.ActionState.Action.ReturnsNull(); + + var sensorRunner = Substitute.For(); + + this.agentType.SensorRunner.Returns(sensorRunner); + this.agentType.GetAllNodes().Returns(new List { goal }); + this.agentType.GetActions().Returns(new List { action }); + this.agentType.GetGoals().Returns(new List { goal }); + + var resolver = Substitute.For(); + + var runner = new AgentTypeJobRunner(this.agentType, resolver); + + // Act + runner.Run(new[] { this.goapActionProvider }); + runner.Dispose(); + + // Assert + action.Received(1).GetCost(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Test] + public void Run_DisabledAction_DoesntGetCost() + { + // Arrange + var goal = Substitute.For(); + goal.Conditions.Returns(new[] { Substitute.For() }); + + var goalResult = Substitute.For(); + goalResult.Goal.Returns(goal); + + var request = Substitute.For(); + request.Goals.Returns(new List { goal }); + + var action = Substitute.For(); + action.IsEnabled(Arg.Any()).Returns(false); + + this.goapActionProvider.CurrentPlan.Returns(goalResult); + this.goapActionProvider.GoalRequest.Returns(request); + this.goapActionProvider.Receiver.ActionState.Action.ReturnsNull(); + + var sensorRunner = Substitute.For(); + + this.agentType.SensorRunner.Returns(sensorRunner); + this.agentType.GetAllNodes().Returns(new List { goal }); + this.agentType.GetActions().Returns(new List { action }); + this.agentType.GetGoals().Returns(new List { goal }); + + var resolver = Substitute.For(); + + var runner = new AgentTypeJobRunner(this.agentType, resolver); + + // Act + runner.Run(new[] { this.goapActionProvider }); + runner.Dispose(); + + // Assert + action.Received(0).GetCost(Arg.Any(), Arg.Any(), Arg.Any()); + } } -} +} \ No newline at end of file