Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Added AsTask methods for various operations to make them use async/await #35

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion MountainGoap/Action.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
/// </summary>
private readonly ExecutorCallback executor;

/// <summary>
/// The executor callback for the action as a Task.
/// </summary>
private readonly ExecutorAsTaskCallback executorAsTask;

/// <summary>
/// The cost callback for the action.
/// </summary>
Expand Down Expand Up @@ -83,6 +88,7 @@
/// <param name="name">Name for the action, for eventing and logging purposes.</param>
/// <param name="permutationSelectors">The permutation selector callback for the action's parameters.</param>
/// <param name="executor">The executor callback for the action.</param>
/// <param name="executorAsTask">The executor callback for the action as a Task.</param>
/// <param name="cost">Cost of the action.</param>
/// <param name="costCallback">Callback for determining the cost of the action.</param>
/// <param name="preconditions">Preconditions required in the world state in order for the action to occur.</param>
Expand All @@ -93,11 +99,13 @@
/// <param name="stateMutator">Callback for modifying state after action execution or evaluation.</param>
/// <param name="stateChecker">Callback for checking state before action execution or evaluation.</param>
/// <param name="stateCostDeltaMultiplier">Callback for multiplier for delta value to provide delta cost.</param>
public Action(string? name = null, Dictionary<string, PermutationSelectorCallback>? permutationSelectors = null, ExecutorCallback? executor = null, float cost = 1f, CostCallback? costCallback = null, Dictionary<string, object?>? preconditions = null, Dictionary<string, ComparisonValuePair>? comparativePreconditions = null, Dictionary<string, object?>? postconditions = null, Dictionary<string, object>? arithmeticPostconditions = null, Dictionary<string, string>? parameterPostconditions = null, StateMutatorCallback? stateMutator = null, StateCheckerCallback? stateChecker = null, StateCostDeltaMultiplierCallback? stateCostDeltaMultiplier = null) {
public Action(string? name = null, Dictionary<string, PermutationSelectorCallback>? permutationSelectors = null, ExecutorCallback? executor = null, float cost = 1f, CostCallback? costCallback = null, Dictionary<string, object?>? preconditions = null, Dictionary<string, ComparisonValuePair>? comparativePreconditions = null, Dictionary<string, object?>? postconditions = null, Dictionary<string, object>? arithmeticPostconditions = null, Dictionary<string, string>? parameterPostconditions = null, StateMutatorCallback? stateMutator = null, StateCheckerCallback? stateChecker = null, StateCostDeltaMultiplierCallback? stateCostDeltaMultiplier = null, ExecutorAsTaskCallback? executorAsTask = null) {
if (permutationSelectors == null) this.permutationSelectors = new();
else this.permutationSelectors = permutationSelectors;
if (executor == null) this.executor = DefaultExecutorCallback;
else this.executor = executor;
if (executorAsTask == null) this.executorAsTask = DefaultExecutorAsTaskCallback;
else this.executorAsTask = executorAsTask;
Name = name ?? $"Action {Guid.NewGuid()} ({this.executor.GetMethodInfo().Name})";
this.cost = cost;
this.costCallback = costCallback ?? DefaultCostCallback;
Expand All @@ -116,18 +124,28 @@
/// </summary>
public StateCostDeltaMultiplierCallback? StateCostDeltaMultiplier { get; set; }

public static float DefaultStateCostDeltaMultiplier(Action? action, string stateKey) => 1f;

Check warning on line 127 in MountainGoap/Action.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Action.DefaultStateCostDeltaMultiplier(Action?, string)'

/// <summary>
/// Event that triggers when an action begins executing.
/// </summary>
public static event BeginExecuteActionEvent OnBeginExecuteAction = (agent, action, parameters) => { };

/// <summary>
/// Event that triggers when an action begins executing as a task.
/// </summary>
public static event BeginExecuteActionAsTaskEvent OnBeginExecuteActionAsTask = (agent, action, parameters) => Task.CompletedTask;

/// <summary>
/// Event that triggers when an action finishes executing.
/// </summary>
public static event FinishExecuteActionEvent OnFinishExecuteAction = (agent, action, status, parameters) => { };

/// <summary>
/// Event that triggers when an action finishes executing as a task.
/// </summary>
public static event FinishExecuteActionAsTaskEvent OnFinishExecuteActionAsTask = (agent, action, status, parameters) => Task.CompletedTask;

/// <summary>
/// Gets or sets the execution status of the action.
/// </summary>
Expand Down Expand Up @@ -197,6 +215,26 @@
}
}

/// <summary>
/// Executes a step of work for the agent as a task.
/// </summary>
/// <param name="agent">Agent executing the action.</param>
/// <returns>A Task with the execution status of the action.</returns>
internal async Task<ExecutionStatus> ExecuteAsTask(Agent agent) {
await OnBeginExecuteActionAsTask(agent, this, parameters);
if (IsPossible(agent.State)) {
var newState = await executorAsTask(agent, this);
if (newState == ExecutionStatus.Succeeded) ApplyEffects(agent.State);
ExecutionStatus = newState;
await OnFinishExecuteActionAsTask(agent, this, ExecutionStatus, parameters);
return newState;
}
else {
await OnFinishExecuteActionAsTask(agent, this, ExecutionStatus.NotPossible, parameters);
return ExecutionStatus.NotPossible;
}
}

/// <summary>
/// Determines whether or not an action is possible.
/// </summary>
Expand Down Expand Up @@ -309,6 +347,16 @@
return ExecutionStatus.Failed;
}

/// <summary>
/// Default executor callback to be used if no callback is passed in.
/// </summary>
/// <param name="agent">Agent executing the action.</param>
/// <param name="action">Action to be executed.</param>
/// <returns>A Task with a Failed status, since the action cannot execute without a callback.</returns>
private static Task<ExecutionStatus> DefaultExecutorAsTaskCallback(Agent agent, Action action) {
return Task.FromResult(ExecutionStatus.Failed);
}

#pragma warning disable S1172 // Unused method parameters should be removed
private static float DefaultCostCallback(Action action, ConcurrentDictionary<string, object?> currentState) {
return action.cost;
Expand Down
72 changes: 72 additions & 0 deletions MountainGoap/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,21 @@ public Agent(string? name = null, ConcurrentDictionary<string, object?>? state =
/// </summary>
public static event AgentStepEvent OnAgentStep = (agent) => { };

/// <summary>
/// Event that fires when the agent executes a step of work as a Task.
/// </summary>
public static event AgentStepAsTaskEvent OnAgentStepAsTask = (agent) => Task.CompletedTask;

/// <summary>
/// Event that fires when an action sequence completes.
/// </summary>
public static event AgentActionSequenceCompletedEvent OnAgentActionSequenceCompleted = (agent) => { };

/// <summary>
/// Event that fires when an action sequence completes as a Task.
/// </summary>
public static event AgentActionSequenceCompletedAsTaskEvent OnAgentActionSequenceCompletedAsTask = (agent) => Task.CompletedTask;

/// <summary>
/// Event that fires when planning begins.
/// </summary>
Expand Down Expand Up @@ -145,6 +155,23 @@ public void Step(StepMode mode = StepMode.Default) {
else if (mode == StepMode.AllActions) while (IsBusy) Execute();
}

/// <summary>
/// Steps forward as a Task for async purposes.
/// </summary>
/// <param name="mode">Mode to use for the step operation.</param>
/// <returns>A Task for async operations.</returns>
public async Task StepAsTask(StepMode mode = StepMode.Default) {
await OnAgentStepAsTask(this);
foreach (var sensor in Sensors) await sensor.RunAsTask(this);
if (mode == StepMode.Default) {
await StepAsyncAsTask();
return;
}
if (!IsBusy) await Task.Run(() => Planner.Plan(this, CostMaximum, StepMaximum));
if (mode == StepMode.OneAction) await ExecuteAsTask();
else if (mode == StepMode.AllActions) while (IsBusy) await ExecuteAsTask();
}

/// <summary>
/// Clears the current action sequences (also known as plans).
/// </summary>
Expand Down Expand Up @@ -173,13 +200,31 @@ public void PlanAsync() {
}
}

/// <summary>
/// Plans as a Task for async purposes.
/// </summary>
/// <returns>A completed Task.</returns>
public Task PlanAsTask() {
if (!IsPlanning) return ExecuteAsTask();
return Task.CompletedTask;
}

/// <summary>
/// Executes the current plan.
/// </summary>
public void ExecutePlan() {
if (!IsPlanning) Execute();
}

/// <summary>
/// Executes plan as a Task for async purposes.
/// </summary>
/// <returns>A completed Task.</returns>
public Task ExecutePlanAsTask() {
if (!IsPlanning) return ExecutePlanAsTask();
return Task.CompletedTask;
}

/// <summary>
/// Triggers OnPlanningStarted event.
/// </summary>
Expand Down Expand Up @@ -247,6 +292,33 @@ private void StepAsync() {
else if (!IsPlanning) Execute();
}

private async Task StepAsyncAsTask() {
if (!IsBusy && !IsPlanning) {
IsPlanning = true;
await Task.Run(() => Planner.Plan(this, CostMaximum, StepMaximum));
IsPlanning = false;
}
else if (!IsPlanning) await ExecuteAsTask();
}

private async Task ExecuteAsTask() {
if (CurrentActionSequences.Count > 0) {
List<List<Action>> cullableSequences = new();
foreach (var sequence in CurrentActionSequences) {
if (sequence.Count > 0) {
var executionStatus = await sequence[0].ExecuteAsTask(this);
if (executionStatus != ExecutionStatus.Executing) sequence.RemoveAt(0);
}
else cullableSequences.Add(sequence);
}
foreach (var sequence in cullableSequences) {
CurrentActionSequences.Remove(sequence);
await OnAgentActionSequenceCompletedAsTask(this);
}
}
else IsBusy = false;
}

/// <summary>
/// Executes the current action sequences.
/// </summary>
Expand Down
10 changes: 9 additions & 1 deletion MountainGoap/CallbackDelegates/ExecutorCallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@

namespace MountainGoap {
/// <summary>
/// Delegate type for a callback that defines a list of all possible parameter states for the given state.
/// Delegate type for a callback that performs an action.
/// </summary>
/// <param name="agent">Agent executing the action.</param>
/// <param name="action">Action being executed.</param>
/// <returns>New execution status of the action.</returns>
public delegate ExecutionStatus ExecutorCallback(Agent agent, Action action);

/// <summary>
/// Delegate type for a callback that performs an action as a Task.
/// </summary>
/// <param name="agent">Agent executing the action.</param>
/// <param name="action">Action being executed.</param>
/// <returns>New execution status of the action.</returns>
public delegate Task<ExecutionStatus> ExecutorAsTaskCallback(Agent agent, Action action);
}
7 changes: 7 additions & 0 deletions MountainGoap/CallbackDelegates/SensorRunCallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ namespace MountainGoap {
/// </summary>
/// <param name="agent">Agent using the sensor.</param>
public delegate void SensorRunCallback(Agent agent);

/// <summary>
/// Delegate type for a callback that runs a sensor as a task during a game loop.
/// </summary>
/// <param name="agent">Agent using the sensor.</param>
/// <returns>A Task for an asynchronous operation.</returns>
public delegate Task SensorRunAsTaskCallback(Agent agent);
}
7 changes: 7 additions & 0 deletions MountainGoap/Events/AgentActionSequenceCompletedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ namespace MountainGoap {
/// </summary>
/// <param name="agent">Agent executing the action sequence.</param>
public delegate void AgentActionSequenceCompletedEvent(Agent agent);

/// <summary>
/// Delegate type for a listener to the event that fires when an agent completes an action sequence as a task.
/// </summary>
/// <param name="agent">Agent executing the action sequence.</param>
/// <returns>A Task for an asynchronous operation.</returns>
public delegate Task AgentActionSequenceCompletedAsTaskEvent(Agent agent);
}
7 changes: 7 additions & 0 deletions MountainGoap/Events/AgentStepEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ namespace MountainGoap {
/// </summary>
/// <param name="agent">Agent executing the step of work.</param>
public delegate void AgentStepEvent(Agent agent);

/// <summary>
/// Delegate type for a listener to the event that fires when an agent executes a step of work as a task.
/// </summary>
/// <param name="agent">Agent executing the step of work.</param>
/// <returns>A Task for an asynchronous operation.</returns>
public delegate Task AgentStepAsTaskEvent(Agent agent);
}
9 changes: 9 additions & 0 deletions MountainGoap/Events/BeginExecuteActionEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,13 @@ namespace MountainGoap {
/// <param name="action">Action being executed.</param>
/// <param name="parameters">Parameters to the action being executed.</param>
public delegate void BeginExecuteActionEvent(Agent agent, Action action, Dictionary<string, object?> parameters);

/// <summary>
/// Delegate type for a listener to the event that fires when an action begins executing as a Task.
/// </summary>
/// <param name="agent">Agent executing the action.</param>
/// <param name="action">Action being executed.</param>
/// <param name="parameters">Parameters to the action being executed.</param>
/// <returns>A Task for an asynchronous operation.</returns>
public delegate Task BeginExecuteActionAsTaskEvent(Agent agent, Action action, Dictionary<string, object?> parameters);
}
10 changes: 10 additions & 0 deletions MountainGoap/Events/FinishExecuteActionEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,14 @@ namespace MountainGoap {
/// <param name="status">Execution status of the action.</param>
/// <param name="parameters">Parameters to the action being executed.</param>
public delegate void FinishExecuteActionEvent(Agent agent, Action action, ExecutionStatus status, Dictionary<string, object?> parameters);

/// <summary>
/// Delegate type for a listener to the event that fires when an action finishes executing as a task.
/// </summary>
/// <param name="agent">Agent executing the action.</param>
/// <param name="action">Action being executed.</param>
/// <param name="status">Execution status of the action.</param>
/// <param name="parameters">Parameters to the action being executed.</param>
/// <returns>A Task for an asynchronous operation.</returns>
public delegate Task FinishExecuteActionAsTaskEvent(Agent agent, Action action, ExecutionStatus status, Dictionary<string, object?> parameters);
}
8 changes: 8 additions & 0 deletions MountainGoap/Events/SensorRunningEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ namespace MountainGoap {
/// <param name="agent">Agent running the sensor.</param>
/// <param name="sensor">Sensor that is about to run.</param>
public delegate void SensorRunEvent(Agent agent, Sensor sensor);

/// <summary>
/// Delegate type for a listener to the event that fires when an agent sensor is about to run as a task.
/// </summary>
/// <param name="agent">Agent running the sensor.</param>
/// <param name="sensor">Sensor that is about to run.</param>
/// <returns>A Task for an asynchronous operation.</returns>
public delegate Task SensorRunAsTaskEvent(Agent agent, Sensor sensor);
}
28 changes: 26 additions & 2 deletions MountainGoap/Sensor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
/// <summary>
/// Callback to be executed when the sensor runs.
/// </summary>
private readonly SensorRunCallback runCallback;
private readonly SensorRunCallback? runCallback;

private readonly SensorRunAsTaskCallback? runAsTaskCallback;

/// <summary>
/// Initializes a new instance of the <see cref="Sensor"/> class.
Expand All @@ -28,20 +30,42 @@
public Sensor(SensorRunCallback runCallback, string? name = null) {
Name = name ?? $"Sensor {Guid.NewGuid()} ({runCallback.GetMethodInfo().Name})";
this.runCallback = runCallback;
runAsTaskCallback = null;
}

/// <summary>
/// Initializes a new instance of the <see cref="Sensor"/> class as a Task-based sensor.
/// </summary>
/// <param name="runAsTaskCallback">Task callback to be executed when the sensor runs.</param>
/// <param name="name">Name of the sensor.</param>
public Sensor(SensorRunAsTaskCallback runAsTaskCallback, string? name = null) {
Name = name ?? $"Sensor {Guid.NewGuid()} ({runAsTaskCallback.GetMethodInfo().Name})";
this.runAsTaskCallback = runAsTaskCallback;
runCallback = null;
}

/// <summary>
/// Event that triggers when a sensor runs.
/// </summary>
public static event SensorRunEvent OnSensorRun = (agent, sensor) => { };

/// <summary>
/// Event that triggers when a sensor runs as a task.
/// </summary>
public static event SensorRunAsTaskEvent OnSensorRunAsTask = (agent, sensor) => Task.CompletedTask;

/// <summary>
/// Runs the sensor during a game loop.
/// </summary>
/// <param name="agent">Agent for which the sensor is being run.</param>
public void Run(Agent agent) {
OnSensorRun(agent, this);
runCallback(agent);
if (runCallback != null) runCallback(agent);
}

public async Task RunAsTask(Agent agent) {

Check warning on line 66 in MountainGoap/Sensor.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'Sensor.RunAsTask(Agent)'
await OnSensorRunAsTask(agent, this);
if (runAsTaskCallback != null) await runAsTaskCallback(agent);
}
}
}
Loading