268 lines
13 KiB
C#
268 lines
13 KiB
C#
|
|
#if GRAPH_DESIGNER
|
|||
|
|
using Opsive.BehaviorDesigner.Runtime.Components;
|
|||
|
|
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
|||
|
|
using Opsive.BehaviorDesigner.Runtime.Utility;
|
|||
|
|
using Opsive.GraphDesigner.Runtime;
|
|||
|
|
using Opsive.GraphDesigner.Runtime.Variables.ECS;
|
|||
|
|
using Opsive.Shared.Utility;
|
|||
|
|
using Unity.Burst;
|
|||
|
|
using Unity.Entities;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
namespace Cielonos.MainGame.Characters.AI
|
|||
|
|
{
|
|||
|
|
[Description("可配置成功/失败策略的高级并行节点。\n" +
|
|||
|
|
"OneSuccess:任一子节点 Success 即整体成功并中止其余分支。\n" +
|
|||
|
|
"AllFailure:单个失败不中止其余分支,全部失败才整体失败。")]
|
|||
|
|
[NodeIcon("f612c025389b22640b1b6df88f4502e7", "8a4a401bcfb527a48a08351efaf92e14")]
|
|||
|
|
[Category("Cielonos")]
|
|||
|
|
public class AdvancedParallel : ECSCompositeTask<AdvancedParallelTaskSystem, AdvancedParallelComponent, AdvancedParallelFlag>, IParentNode, IParallelNode
|
|||
|
|
{
|
|||
|
|
[Tooltip("OneSuccess:任一子节点成功即整体成功。AllSuccess:全部子节点成功才整体成功。")]
|
|||
|
|
[SerializeField] private SuccessPolicy m_SuccessPolicy = SuccessPolicy.AllSuccess;
|
|||
|
|
|
|||
|
|
[Tooltip("OneFailure:任一子节点失败即整体失败。AllFailure:所有子节点失败才整体失败,单个失败不中止其它分支。")]
|
|||
|
|
[SerializeField] private FailurePolicy m_FailurePolicy = FailurePolicy.AllFailure;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Adds the IBufferElementData and interrupt components to the entity.
|
|||
|
|
/// </summary>
|
|||
|
|
public override int AddBufferElement(World world, Entity entity, ECSVariableRegistry registry, GameObject gameObject)
|
|||
|
|
{
|
|||
|
|
var index = base.AddBufferElement(world, entity, registry, gameObject);
|
|||
|
|
ComponentUtility.AddInterruptComponents(world.EntityManager, entity);
|
|||
|
|
return index;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Passes task configuration into the ECS buffer element at tree initialization.
|
|||
|
|
/// </summary>
|
|||
|
|
public override AdvancedParallelComponent GetBufferElement()
|
|||
|
|
{
|
|||
|
|
return new AdvancedParallelComponent
|
|||
|
|
{
|
|||
|
|
Index = RuntimeIndex,
|
|||
|
|
OneSuccessPolicy = m_SuccessPolicy == SuccessPolicy.OneSuccess,
|
|||
|
|
AllFailurePolicy = m_FailurePolicy == FailurePolicy.AllFailure
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override void Reset()
|
|||
|
|
{
|
|||
|
|
base.Reset();
|
|||
|
|
m_SuccessPolicy = SuccessPolicy.OneSuccess;
|
|||
|
|
m_FailurePolicy = FailurePolicy.AllFailure;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// ECS buffer element storing the runtime index and policy flags for AdvancedParallel.
|
|||
|
|
/// </summary>
|
|||
|
|
public struct AdvancedParallelComponent : IBufferElementData
|
|||
|
|
{
|
|||
|
|
[SerializeField] private ushort m_Index;
|
|||
|
|
[SerializeField] private bool m_OneSuccessPolicy;
|
|||
|
|
[SerializeField] private bool m_AllFailurePolicy;
|
|||
|
|
|
|||
|
|
public ushort Index { get => m_Index; set => m_Index = value; }
|
|||
|
|
public bool OneSuccessPolicy { get => m_OneSuccessPolicy; set => m_OneSuccessPolicy = value; }
|
|||
|
|
public bool AllFailurePolicy { get => m_AllFailurePolicy; set => m_AllFailurePolicy = value; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// ECS tag indicating an AdvancedParallel node is currently active.
|
|||
|
|
/// </summary>
|
|||
|
|
public struct AdvancedParallelFlag : IComponentData, IEnableableComponent { }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Runs the AdvancedParallel evaluation logic each frame via Burst-compiled ECS job.
|
|||
|
|
/// </summary>
|
|||
|
|
[DisableAutoCreation]
|
|||
|
|
public partial struct AdvancedParallelTaskSystem : ISystem
|
|||
|
|
{
|
|||
|
|
private EntityQuery m_Query;
|
|||
|
|
|
|||
|
|
private void OnCreate(ref SystemState state)
|
|||
|
|
{
|
|||
|
|
m_Query = SystemAPI.QueryBuilder()
|
|||
|
|
.WithAllRW<BranchComponent>()
|
|||
|
|
.WithAllRW<TaskComponent>()
|
|||
|
|
.WithAllRW<AdvancedParallelComponent>()
|
|||
|
|
.WithAll<AdvancedParallelFlag, EvaluateFlag>()
|
|||
|
|
.Build();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[BurstCompile]
|
|||
|
|
private void OnUpdate(ref SystemState state)
|
|||
|
|
{
|
|||
|
|
var ecb = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>()
|
|||
|
|
.CreateCommandBuffer(state.WorldUnmanaged);
|
|||
|
|
state.Dependency = new AdvancedParallelJob
|
|||
|
|
{
|
|||
|
|
EntityCommandBuffer = ecb.AsParallelWriter()
|
|||
|
|
}.ScheduleParallel(m_Query, state.Dependency);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[BurstCompile]
|
|||
|
|
private partial struct AdvancedParallelJob : IJobEntity
|
|||
|
|
{
|
|||
|
|
public EntityCommandBuffer.ParallelWriter EntityCommandBuffer;
|
|||
|
|
|
|||
|
|
[BurstCompile]
|
|||
|
|
public void Execute(
|
|||
|
|
Entity entity,
|
|||
|
|
[EntityIndexInQuery] int entityIndex,
|
|||
|
|
ref DynamicBuffer<AdvancedParallelComponent> parallelComponents,
|
|||
|
|
ref DynamicBuffer<TaskComponent> taskComponents,
|
|||
|
|
ref DynamicBuffer<BranchComponent> branchComponents)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < parallelComponents.Length; ++i)
|
|||
|
|
{
|
|||
|
|
var parallelComponent = parallelComponents[i];
|
|||
|
|
var taskComponent = taskComponents[parallelComponent.Index];
|
|||
|
|
var taskStatus = taskComponent.Status;
|
|||
|
|
|
|||
|
|
if (taskStatus != TaskStatus.Queued && taskStatus != TaskStatus.Running)
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
var branchComponent = branchComponents[taskComponent.BranchIndex];
|
|||
|
|
if (branchComponent.InterruptType != InterruptType.None || !branchComponent.CanExecute)
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
ushort childIndex;
|
|||
|
|
TaskComponent childTaskComponent;
|
|||
|
|
|
|||
|
|
// ── Initialize all children on first activation ──────────────────────────
|
|||
|
|
if (taskStatus == TaskStatus.Queued)
|
|||
|
|
{
|
|||
|
|
taskComponent.Status = TaskStatus.Running;
|
|||
|
|
taskComponents[taskComponent.Index] = taskComponent;
|
|||
|
|
|
|||
|
|
childIndex = (ushort)(parallelComponent.Index + 1);
|
|||
|
|
while (childIndex != ushort.MaxValue)
|
|||
|
|
{
|
|||
|
|
childTaskComponent = taskComponents[childIndex];
|
|||
|
|
if (childTaskComponent.Status != TaskStatus.Queued)
|
|||
|
|
{
|
|||
|
|
childTaskComponent.Status = TaskStatus.Queued;
|
|||
|
|
taskComponents[childIndex] = childTaskComponent;
|
|||
|
|
}
|
|||
|
|
var initBranch = branchComponents[childTaskComponent.BranchIndex];
|
|||
|
|
if (initBranch.NextIndex != childTaskComponent.Index)
|
|||
|
|
{
|
|||
|
|
initBranch.NextIndex = childTaskComponent.Index;
|
|||
|
|
branchComponents[childTaskComponent.BranchIndex] = initBranch;
|
|||
|
|
}
|
|||
|
|
childIndex = childTaskComponent.SiblingIndex;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Scan children and collect status flags ───────────────────────────────
|
|||
|
|
var anyRunning = false;
|
|||
|
|
var anySuccess = false;
|
|||
|
|
var anyFailure = false;
|
|||
|
|
|
|||
|
|
childIndex = (ushort)(parallelComponent.Index + 1);
|
|||
|
|
while (childIndex != ushort.MaxValue)
|
|||
|
|
{
|
|||
|
|
childTaskComponent = taskComponents[childIndex];
|
|||
|
|
|
|||
|
|
if (childTaskComponent.Status == TaskStatus.Queued ||
|
|||
|
|
childTaskComponent.Status == TaskStatus.Running)
|
|||
|
|
{
|
|||
|
|
anyRunning = true;
|
|||
|
|
}
|
|||
|
|
else if (childTaskComponent.Status == TaskStatus.Success)
|
|||
|
|
{
|
|||
|
|
anySuccess = true;
|
|||
|
|
// Mark this branch as done so it doesn't keep executing.
|
|||
|
|
var childBranch = branchComponents[childTaskComponent.BranchIndex];
|
|||
|
|
if (childBranch.ActiveIndex != ushort.MaxValue && childBranch.NextIndex != ushort.MaxValue)
|
|||
|
|
{
|
|||
|
|
childBranch.NextIndex = ushort.MaxValue;
|
|||
|
|
branchComponents[childTaskComponent.BranchIndex] = childBranch;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (childTaskComponent.Status == TaskStatus.Failure)
|
|||
|
|
{
|
|||
|
|
anyFailure = true;
|
|||
|
|
// Stop this branch. Other branches continue when AllFailure policy is active.
|
|||
|
|
var childBranch = branchComponents[childTaskComponent.BranchIndex];
|
|||
|
|
if (childBranch.NextIndex != ushort.MaxValue)
|
|||
|
|
{
|
|||
|
|
childBranch.NextIndex = ushort.MaxValue;
|
|||
|
|
branchComponents[childTaskComponent.BranchIndex] = childBranch;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
childIndex = childTaskComponent.SiblingIndex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Apply success/failure policies ───────────────────────────────────────
|
|||
|
|
bool allTerminated = !anyRunning;
|
|||
|
|
|
|||
|
|
// Success condition
|
|||
|
|
bool shouldSucceed = parallelComponent.OneSuccessPolicy
|
|||
|
|
? anySuccess // OneSuccess: any child succeeded
|
|||
|
|
: allTerminated && !anyFailure; // AllSuccess: all done with no failures
|
|||
|
|
|
|||
|
|
// Failure condition (only evaluated when success hasn't triggered)
|
|||
|
|
bool shouldFail = false;
|
|||
|
|
if (!shouldSucceed)
|
|||
|
|
{
|
|||
|
|
shouldFail = !parallelComponent.AllFailurePolicy
|
|||
|
|
? anyFailure // OneFailure: any single failure → fail immediately
|
|||
|
|
: allTerminated; // AllFailure: fail only when nothing is still running
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!shouldSucceed && !shouldFail)
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
// ── Stop all remaining running/queued children ───────────────────────────
|
|||
|
|
ushort maxChildIndex = taskComponent.ChildUpperIndex > taskComponent.Index
|
|||
|
|
? taskComponent.ChildUpperIndex
|
|||
|
|
: (ushort)taskComponent.Index;
|
|||
|
|
|
|||
|
|
var interruptedFlagSet = false;
|
|||
|
|
for (ushort j = (ushort)(taskComponent.Index + 1); j <= maxChildIndex; ++j)
|
|||
|
|
{
|
|||
|
|
childTaskComponent = taskComponents[j];
|
|||
|
|
if (childTaskComponent.Status == TaskStatus.Running ||
|
|||
|
|
childTaskComponent.Status == TaskStatus.Queued)
|
|||
|
|
{
|
|||
|
|
childTaskComponent.Status = TaskStatus.Failure;
|
|||
|
|
taskComponents[j] = childTaskComponent;
|
|||
|
|
|
|||
|
|
if (!interruptedFlagSet)
|
|||
|
|
{
|
|||
|
|
EntityCommandBuffer.SetComponentEnabled<InterruptedFlag>(entityIndex, entity, true);
|
|||
|
|
interruptedFlagSet = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
branchComponent = branchComponents[childTaskComponent.BranchIndex];
|
|||
|
|
if (branchComponent.ActiveIndex == childTaskComponent.Index &&
|
|||
|
|
branchComponent.NextIndex != ushort.MaxValue)
|
|||
|
|
{
|
|||
|
|
branchComponent.NextIndex = ushort.MaxValue;
|
|||
|
|
branchComponents[childTaskComponent.BranchIndex] = branchComponent;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Propagate result to parent ────────────────────────────────────────────
|
|||
|
|
branchComponent = branchComponents[taskComponent.BranchIndex];
|
|||
|
|
if (branchComponent.NextIndex != taskComponent.ParentIndex)
|
|||
|
|
{
|
|||
|
|
branchComponent.NextIndex = taskComponent.ParentIndex;
|
|||
|
|
branchComponents[taskComponent.BranchIndex] = branchComponent;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
taskComponent.Status = shouldSucceed ? TaskStatus.Success : TaskStatus.Failure;
|
|||
|
|
taskComponents[taskComponent.Index] = taskComponent;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endif
|