You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
11 KiB
C#
254 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using FluidHTN.PrimitiveTasks;
|
|
|
|
namespace FluidHTN.Compounds
|
|
{
|
|
public class Selector : CompoundTask
|
|
{
|
|
// ========================================================= FIELDS
|
|
|
|
protected readonly Queue<ITask> Plan = new Queue<ITask>();
|
|
|
|
// ========================================================= VALIDITY
|
|
|
|
public override bool IsValid(IContext ctx)
|
|
{
|
|
// Check that our preconditions are valid first.
|
|
if (base.IsValid(ctx) == false)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.IsValid:Failed:Preconditions not met!", ConsoleColor.Red);
|
|
return false;
|
|
}
|
|
|
|
// Selector requires there to be at least one sub-task to successfully select from.
|
|
if (Subtasks.Count == 0)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.IsValid:Failed:No sub-tasks!", ConsoleColor.Red);
|
|
return false;
|
|
}
|
|
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.IsValid:Success!", ConsoleColor.Green);
|
|
return true;
|
|
}
|
|
|
|
private bool BeatsLastMTR(IContext ctx, int taskIndex, int currentDecompositionIndex)
|
|
{
|
|
// If the last plan's traversal record for this decomposition layer
|
|
// has a smaller index than the current task index we're about to
|
|
// decompose, then the new decomposition can't possibly beat the
|
|
// running plan, so we cancel finding a new plan.
|
|
if (ctx.LastMTR[currentDecompositionIndex] < taskIndex)
|
|
{
|
|
// But, if any of the earlier records beat the record in LastMTR, we're still good, as we're on a higher priority branch.
|
|
// This ensures that [0,0,1] can beat [0,1,0]
|
|
for (var i = 0; i < ctx.MethodTraversalRecord.Count; i++)
|
|
{
|
|
var diff = ctx.MethodTraversalRecord[i] - ctx.LastMTR[i];
|
|
if (diff < 0)
|
|
{
|
|
return true;
|
|
}
|
|
if (diff > 0)
|
|
{
|
|
// We should never really be able to get here, but just in case.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ========================================================= DECOMPOSITION
|
|
|
|
/// <summary>
|
|
/// In a Selector decomposition, just a single sub-task must be valid and successfully decompose for the Selector to be
|
|
/// successfully decomposed.
|
|
/// </summary>
|
|
/// <param name="ctx"></param>
|
|
/// <returns></returns>
|
|
protected override DecompositionStatus OnDecompose(IContext ctx, int startIndex, out Queue<ITask> result)
|
|
{
|
|
Plan.Clear();
|
|
|
|
for (var taskIndex = startIndex; taskIndex < Subtasks.Count; taskIndex++)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecompose:Task index: {taskIndex}: {Subtasks[taskIndex]?.Name}");
|
|
// If the last plan is still running, we need to check whether the
|
|
// new decomposition can possibly beat it.
|
|
if (ctx.LastMTR != null && ctx.LastMTR.Count > 0)
|
|
{
|
|
if (ctx.MethodTraversalRecord.Count < ctx.LastMTR.Count)
|
|
{
|
|
var currentDecompositionIndex = ctx.MethodTraversalRecord.Count;
|
|
if (BeatsLastMTR(ctx, taskIndex, currentDecompositionIndex) == false)
|
|
{
|
|
ctx.MethodTraversalRecord.Add(-1);
|
|
if (ctx.DebugMTR) ctx.MTRDebug.Add($"REPLAN FAIL {Subtasks[taskIndex].Name}");
|
|
|
|
if (ctx.LogDecomposition)
|
|
Log(ctx,
|
|
$"Selector.OnDecompose:Rejected:Index {currentDecompositionIndex} is beat by last method traversal record!", ConsoleColor.Red);
|
|
result = null;
|
|
return DecompositionStatus.Rejected;
|
|
}
|
|
}
|
|
}
|
|
|
|
var task = Subtasks[taskIndex];
|
|
|
|
var status = OnDecomposeTask(ctx, task, taskIndex, null, out result);
|
|
switch (status)
|
|
{
|
|
case DecompositionStatus.Rejected:
|
|
case DecompositionStatus.Succeeded:
|
|
case DecompositionStatus.Partial:
|
|
return status;
|
|
case DecompositionStatus.Failed:
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
result = Plan;
|
|
return result.Count == 0 ? DecompositionStatus.Failed : DecompositionStatus.Succeeded;
|
|
}
|
|
|
|
protected override DecompositionStatus OnDecomposeTask(IContext ctx, ITask task, int taskIndex,
|
|
int[] oldStackDepth, out Queue<ITask> result)
|
|
{
|
|
if (task.IsValid(ctx) == false)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeTask:Failed:Task {task.Name}.IsValid returned false!", ConsoleColor.Red);
|
|
result = Plan;
|
|
return task.OnIsValidFailed(ctx);
|
|
}
|
|
|
|
if (task is ICompoundTask compoundTask)
|
|
{
|
|
return OnDecomposeCompoundTask(ctx, compoundTask, taskIndex, null, out result);
|
|
}
|
|
|
|
if (task is IPrimitiveTask primitiveTask)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeTask:Pushed {primitiveTask.Name} to plan!", ConsoleColor.Blue);
|
|
primitiveTask.ApplyEffects(ctx);
|
|
Plan.Enqueue(task);
|
|
}
|
|
|
|
if (task is Slot slot)
|
|
{
|
|
return OnDecomposeSlot(ctx, slot, taskIndex, null, out result);
|
|
}
|
|
|
|
result = Plan;
|
|
var status = result.Count == 0 ? DecompositionStatus.Failed : DecompositionStatus.Succeeded;
|
|
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeTask:{status}!", status == DecompositionStatus.Succeeded ? ConsoleColor.Green : ConsoleColor.Red);
|
|
return status;
|
|
}
|
|
|
|
protected override DecompositionStatus OnDecomposeCompoundTask(IContext ctx, ICompoundTask task, int taskIndex,
|
|
int[] oldStackDepth, out Queue<ITask> result)
|
|
{
|
|
// We need to record the task index before we decompose the task,
|
|
// so that the traversal record is set up in the right order.
|
|
ctx.MethodTraversalRecord.Add(taskIndex);
|
|
if (ctx.DebugMTR) ctx.MTRDebug.Add(task.Name);
|
|
|
|
var status = task.Decompose(ctx, 0, out var subPlan);
|
|
|
|
// If status is rejected, that means the entire planning procedure should cancel.
|
|
if (status == DecompositionStatus.Rejected)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeCompoundTask:{status}: Decomposing {task.Name} was rejected.", ConsoleColor.Red);
|
|
result = null;
|
|
return DecompositionStatus.Rejected;
|
|
}
|
|
|
|
// If the decomposition failed
|
|
if (status == DecompositionStatus.Failed)
|
|
{
|
|
// Remove the taskIndex if it failed to decompose.
|
|
ctx.MethodTraversalRecord.RemoveAt(ctx.MethodTraversalRecord.Count - 1);
|
|
if (ctx.DebugMTR) ctx.MTRDebug.RemoveAt(ctx.MTRDebug.Count - 1);
|
|
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeCompoundTask:{status}: Decomposing {task.Name} failed.", ConsoleColor.Red);
|
|
result = Plan;
|
|
return DecompositionStatus.Failed;
|
|
}
|
|
|
|
while (subPlan.Count > 0)
|
|
{
|
|
var p = subPlan.Dequeue();
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeCompoundTask:Decomposing {task.Name}:Pushed {p.Name} to plan!", ConsoleColor.Blue);
|
|
Plan.Enqueue(p);
|
|
}
|
|
|
|
if (ctx.HasPausedPartialPlan)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeCompoundTask:Return partial plan at index {taskIndex}!", ConsoleColor.DarkBlue);
|
|
result = Plan;
|
|
return DecompositionStatus.Partial;
|
|
}
|
|
|
|
result = Plan;
|
|
var s = result.Count == 0 ? DecompositionStatus.Failed : DecompositionStatus.Succeeded;
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeCompoundTask:{s}!", s == DecompositionStatus.Succeeded ? ConsoleColor.Green : ConsoleColor.Red);
|
|
return s;
|
|
}
|
|
|
|
protected override DecompositionStatus OnDecomposeSlot(IContext ctx, Slot task, int taskIndex, int[] oldStackDepth, out Queue<ITask> result)
|
|
{
|
|
// We need to record the task index before we decompose the task,
|
|
// so that the traversal record is set up in the right order.
|
|
ctx.MethodTraversalRecord.Add(taskIndex);
|
|
if (ctx.DebugMTR) ctx.MTRDebug.Add(task.Name);
|
|
|
|
var status = task.Decompose(ctx, 0, out var subPlan);
|
|
|
|
// If status is rejected, that means the entire planning procedure should cancel.
|
|
if (status == DecompositionStatus.Rejected)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeSlot:{status}: Decomposing {task.Name} was rejected.", ConsoleColor.Red);
|
|
result = null;
|
|
return DecompositionStatus.Rejected;
|
|
}
|
|
|
|
// If the decomposition failed
|
|
if (status == DecompositionStatus.Failed)
|
|
{
|
|
// Remove the taskIndex if it failed to decompose.
|
|
ctx.MethodTraversalRecord.RemoveAt(ctx.MethodTraversalRecord.Count - 1);
|
|
if (ctx.DebugMTR) ctx.MTRDebug.RemoveAt(ctx.MTRDebug.Count - 1);
|
|
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeSlot:{status}: Decomposing {task.Name} failed.", ConsoleColor.Red);
|
|
result = Plan;
|
|
return DecompositionStatus.Failed;
|
|
}
|
|
|
|
while (subPlan.Count > 0)
|
|
{
|
|
var p = subPlan.Dequeue();
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeSlot:Decomposing {task.Name}:Pushed {p.Name} to plan!", ConsoleColor.Blue);
|
|
Plan.Enqueue(p);
|
|
}
|
|
|
|
if (ctx.HasPausedPartialPlan)
|
|
{
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeSlot:Return partial plan!", ConsoleColor.DarkBlue);
|
|
result = Plan;
|
|
return DecompositionStatus.Partial;
|
|
}
|
|
|
|
result = Plan;
|
|
var s = result.Count == 0 ? DecompositionStatus.Failed : DecompositionStatus.Succeeded;
|
|
if (ctx.LogDecomposition) Log(ctx, $"Selector.OnDecomposeSlot:{s}!", s == DecompositionStatus.Succeeded ? ConsoleColor.Green : ConsoleColor.Red);
|
|
return s;
|
|
}
|
|
}
|
|
}
|