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.

239 lines
9.1 KiB
C#

using System;
using System.Collections.Generic;
using FluidHTN.Compounds;
namespace FluidHTN
{
public class Domain<T> : IDomain where T : IContext
{
// ========================================================= FIELDS
private Dictionary<int, Slot> _slots = null;
// ========================================================= CONSTRUCTION
public Domain(string name)
{
Root = new TaskRoot { Name = name, Parent = null };
}
// ========================================================= PROPERTIES
public TaskRoot Root { get; }
// ========================================================= HIERARCHY HANDLING
public void Add(ICompoundTask parent, ITask subtask)
{
if (parent == subtask)
throw new Exception("Parent-task and Sub-task can't be the same instance!");
parent.AddSubtask(subtask);
subtask.Parent = parent;
}
public void Add(ICompoundTask parent, Slot slot)
{
if (parent == slot)
throw new Exception("Parent-task and Sub-task can't be the same instance!");
if (_slots != null)
{
if (_slots.ContainsKey(slot.SlotId))
{
throw new Exception("This slot id already exist in the domain definition!");
}
}
parent.AddSubtask(slot);
slot.Parent = parent;
if(_slots == null)
{
_slots = new Dictionary<int, Slot>();
}
_slots.Add(slot.SlotId, slot);
}
// ========================================================= PLANNING
public DecompositionStatus FindPlan(T ctx, out Queue<ITask> plan)
{
if (ctx.IsInitialized == false)
throw new Exception("Context was not initialized!");
if (ctx.MethodTraversalRecord == null)
throw new Exception("We require the Method Traversal Record to have a valid instance.");
ctx.ContextState = ContextState.Planning;
plan = null;
var status = DecompositionStatus.Rejected;
// We first check whether we have a stored start task. This is true
// if we had a partial plan pause somewhere in our plan, and we now
// want to continue where we left off.
// If this is the case, we don't erase the MTR, but continue building it.
// However, if we have a partial plan, but LastMTR is not 0, that means
// that the partial plan is still running, but something triggered a replan.
// When this happens, we have to plan from the domain root (we're not
// continuing the current plan), so that we're open for other plans to replace
// the running partial plan.
if (ctx.HasPausedPartialPlan && ctx.LastMTR.Count == 0)
{
ctx.HasPausedPartialPlan = false;
while (ctx.PartialPlanQueue.Count > 0)
{
var kvp = ctx.PartialPlanQueue.Dequeue();
if (plan == null)
{
status = kvp.Task.Decompose(ctx, kvp.TaskIndex, out plan);
}
else
{
status = kvp.Task.Decompose(ctx, kvp.TaskIndex, out var p);
if (status == DecompositionStatus.Succeeded || status == DecompositionStatus.Partial)
{
while (p.Count > 0)
{
plan.Enqueue(p.Dequeue());
}
}
}
// While continuing a partial plan, we might encounter
// a new pause.
if (ctx.HasPausedPartialPlan)
break;
}
// If we failed to continue the paused partial plan,
// then we have to start planning from the root.
if (status == DecompositionStatus.Rejected || status == DecompositionStatus.Failed)
{
ctx.MethodTraversalRecord.Clear();
if (ctx.DebugMTR) ctx.MTRDebug.Clear();
status = Root.Decompose(ctx, 0, out plan);
}
}
else
{
Queue<PartialPlanEntry> lastPartialPlanQueue = null;
if (ctx.HasPausedPartialPlan)
{
ctx.HasPausedPartialPlan = false;
lastPartialPlanQueue = ctx.Factory.CreateQueue<PartialPlanEntry>();
while (ctx.PartialPlanQueue.Count > 0)
{
lastPartialPlanQueue.Enqueue(ctx.PartialPlanQueue.Dequeue());
}
}
// We only erase the MTR if we start from the root task of the domain.
ctx.MethodTraversalRecord.Clear();
if (ctx.DebugMTR) ctx.MTRDebug.Clear();
status = Root.Decompose(ctx, 0, out plan);
// If we failed to find a new plan, we have to restore the old plan,
// if it was a partial plan.
if (lastPartialPlanQueue != null)
{
if (status == DecompositionStatus.Rejected || status == DecompositionStatus.Failed)
{
ctx.HasPausedPartialPlan = true;
ctx.PartialPlanQueue.Clear();
while (lastPartialPlanQueue.Count > 0)
{
ctx.PartialPlanQueue.Enqueue(lastPartialPlanQueue.Dequeue());
}
ctx.Factory.FreeQueue(ref lastPartialPlanQueue);
}
}
}
// If this MTR equals the last MTR, then we need to double check whether we ended up
// just finding the exact same plan. During decomposition each compound task can't check
// for equality, only for less than, so this case needs to be treated after the fact.
var isMTRsEqual = ctx.MethodTraversalRecord.Count == ctx.LastMTR.Count;
if (isMTRsEqual)
{
for (var i = 0; i < ctx.MethodTraversalRecord.Count; i++)
if (ctx.MethodTraversalRecord[i] < ctx.LastMTR[i])
{
isMTRsEqual = false;
break;
}
if (isMTRsEqual)
{
plan = null;
status = DecompositionStatus.Rejected;
}
}
if (status == DecompositionStatus.Succeeded || status == DecompositionStatus.Partial)
{
// Trim away any plan-only or plan&execute effects from the world state change stack, that only
// permanent effects on the world state remains now that the planning is done.
ctx.TrimForExecution();
// Apply permanent world state changes to the actual world state used during plan execution.
for (var i = 0; i < ctx.WorldStateChangeStack.Length; i++)
{
var stack = ctx.WorldStateChangeStack[i];
if (stack != null && stack.Count > 0)
{
ctx.WorldState[i] = stack.Peek().Value;
stack.Clear();
}
}
}
else
{
// Clear away any changes that might have been applied to the stack
// No changes should be made or tracked further when the plan failed.
for (var i = 0; i < ctx.WorldStateChangeStack.Length; i++)
{
var stack = ctx.WorldStateChangeStack[i];
if (stack != null && stack.Count > 0) stack.Clear();
}
}
ctx.ContextState = ContextState.Executing;
return status;
}
// ========================================================= SLOTS
/// <summary>
/// At runtime, set a sub-domain to the slot with the given id.
/// This can be used with Smart Objects, to extend the behavior
/// of an agent at runtime.
/// </summary>
public bool TrySetSlotDomain(int slotId, Domain<T> subDomain)
{
if(_slots != null && _slots.TryGetValue(slotId, out var slot))
{
return slot.Set(subDomain.Root);
}
return false;
}
/// <summary>
/// At runtime, clear the sub-domain from the slot with the given id.
/// This can be used with Smart Objects, to extend the behavior
/// of an agent at runtime.
/// </summary>
public void ClearSlot(int slotId)
{
if (_slots != null && _slots.TryGetValue(slotId, out var slot))
{
slot.Clear();
}
}
}
}