e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
911 lines
40 KiB
C#
911 lines
40 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="InternalState.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Activities.Statements
|
|
{
|
|
using System;
|
|
using System.Activities;
|
|
using System.Activities.DynamicUpdate;
|
|
using System.Activities.Statements.Tracking;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Runtime;
|
|
|
|
/// <summary>
|
|
/// InternalState is internal representation of State.
|
|
/// </summary>
|
|
sealed class InternalState : NativeActivity<string>
|
|
{
|
|
// State denotes corresponding State object.
|
|
State state;
|
|
|
|
// internal representation of transitions.
|
|
Collection<InternalTransition> internalTransitions;
|
|
|
|
// number of running triggers
|
|
Variable<int> currentRunningTriggers;
|
|
Variable<bool> isExiting;
|
|
|
|
// This bookmark is used to evaluate condition of a transition of this state.
|
|
Variable<Bookmark> evaluateConditionBookmark;
|
|
|
|
// Callback which is called when Entry is completed.
|
|
CompletionCallback onEntryComplete;
|
|
|
|
// Callback which is called when Trigger is completed.
|
|
CompletionCallback onTriggerComplete;
|
|
|
|
// Callback which is called when Condition is completed.
|
|
CompletionCallback<bool> onConditionComplete;
|
|
|
|
// Callback which is called when Exit is completed.
|
|
CompletionCallback onExitComplete;
|
|
|
|
// Callback which is used to start to evaluate Condition of a transition of this state.
|
|
BookmarkCallback evaluateConditionCallback;
|
|
|
|
Dictionary<Activity, InternalTransition> triggerInternalTransitionMapping = new Dictionary<Activity, InternalTransition>();
|
|
|
|
public InternalState(State state)
|
|
{
|
|
this.state = state;
|
|
this.DisplayName = state.DisplayName;
|
|
|
|
this.onEntryComplete = new CompletionCallback(this.OnEntryComplete);
|
|
this.onTriggerComplete = new CompletionCallback(this.OnTriggerComplete);
|
|
this.onConditionComplete = new CompletionCallback<bool>(this.OnConditionComplete);
|
|
this.onExitComplete = new CompletionCallback(this.OnExitComplete);
|
|
|
|
this.evaluateConditionCallback = new BookmarkCallback(this.StartEvaluateCondition);
|
|
|
|
this.currentRunningTriggers = new Variable<int>();
|
|
this.isExiting = new Variable<bool>();
|
|
this.evaluateConditionBookmark = new Variable<Bookmark>();
|
|
this.internalTransitions = new Collection<InternalTransition>();
|
|
this.triggerInternalTransitionMapping = new Dictionary<Activity, InternalTransition>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets EventManager is used to globally manage event queue such that triggered events can be processed in order.
|
|
/// </summary>
|
|
[RequiredArgument]
|
|
public InArgument<StateMachineEventManager> EventManager
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets Entry activity that will be executed when state is entering.
|
|
/// </summary>
|
|
public Activity Entry
|
|
{
|
|
get
|
|
{
|
|
return this.state.Entry;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets Exit activity that will be executed when state is leaving.
|
|
/// </summary>
|
|
public Activity Exit
|
|
{
|
|
get
|
|
{
|
|
return this.state.Exit;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether this state is a final state or not.
|
|
/// </summary>
|
|
[DefaultValue(false)]
|
|
public bool IsFinal
|
|
{
|
|
get
|
|
{
|
|
return this.state.IsFinal;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets StateId, which is the identifier of a state. It's unique within a StateMachine.
|
|
/// </summary>
|
|
public string StateId
|
|
{
|
|
get
|
|
{
|
|
return this.state.StateId;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets Transitions collection contains transitions on this state.
|
|
/// </summary>
|
|
public Collection<Transition> Transitions
|
|
{
|
|
get
|
|
{
|
|
return this.state.Transitions;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets Variables collection contains Variables on this state.
|
|
/// </summary>
|
|
public Collection<Variable> Variables
|
|
{
|
|
get
|
|
{
|
|
return this.state.Variables;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the display name of the parent state machine of the state.
|
|
/// Used for tracking purpose only.
|
|
/// </summary>
|
|
public string StateMachineName
|
|
{
|
|
get
|
|
{
|
|
return this.state.StateMachineName;
|
|
}
|
|
}
|
|
|
|
protected override bool CanInduceIdle
|
|
{
|
|
get
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
protected override void CacheMetadata(NativeActivityMetadata metadata)
|
|
{
|
|
this.internalTransitions.Clear();
|
|
|
|
if (this.Entry != null)
|
|
{
|
|
metadata.AddChild(this.Entry);
|
|
}
|
|
|
|
if (this.Exit != null)
|
|
{
|
|
metadata.AddChild(this.Exit);
|
|
}
|
|
|
|
this.ProcessTransitions(metadata);
|
|
metadata.SetVariablesCollection(this.Variables);
|
|
|
|
RuntimeArgument eventManagerArgument = new RuntimeArgument("EventManager", this.EventManager.ArgumentType, ArgumentDirection.In);
|
|
metadata.Bind(this.EventManager, eventManagerArgument);
|
|
|
|
metadata.SetArgumentsCollection(
|
|
new Collection<RuntimeArgument>
|
|
{
|
|
eventManagerArgument
|
|
});
|
|
|
|
metadata.AddImplementationVariable(this.currentRunningTriggers);
|
|
metadata.AddImplementationVariable(this.isExiting);
|
|
metadata.AddImplementationVariable(this.evaluateConditionBookmark);
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0",
|
|
Justification = "The context is used by workflow runtime. The parameter should be fine.")]
|
|
protected override void Execute(NativeActivityContext context)
|
|
{
|
|
StateMachineEventManager eventManager = this.EventManager.Get(context);
|
|
eventManager.CurrentBeingProcessedEvent = null;
|
|
this.isExiting.Set(context, false);
|
|
this.ScheduleEntry(context);
|
|
}
|
|
|
|
protected override void Abort(NativeActivityAbortContext context)
|
|
{
|
|
this.RemoveActiveBookmark(context);
|
|
base.Abort(context);
|
|
}
|
|
|
|
protected override void Cancel(NativeActivityContext context)
|
|
{
|
|
this.RemoveActiveBookmark(context);
|
|
base.Cancel(context);
|
|
}
|
|
|
|
protected override void OnCreateDynamicUpdateMap(NativeActivityUpdateMapMetadata metadata, Activity originalActivity)
|
|
{
|
|
InternalState originalInternalState = (InternalState)originalActivity;
|
|
|
|
// NOTE: State.Entry/Exit are allowed to be removed, because it doesn't change the execution semantics of SM
|
|
// if this removed activity was executing, WF runtime would disallow the update.
|
|
Activity entryActivityMatch = metadata.GetMatch(this.Entry);
|
|
Activity exitActivityMatch = metadata.GetMatch(this.Exit);
|
|
|
|
if ((null != entryActivityMatch && !object.ReferenceEquals(entryActivityMatch, originalInternalState.Entry)) ||
|
|
(null != exitActivityMatch && !object.ReferenceEquals(exitActivityMatch, originalInternalState.Exit)))
|
|
{
|
|
// original State.Entry/Exit is replaced with another child activities with InternalState
|
|
// new State.Entry/Exit is moved from another child activities within InternalState.
|
|
metadata.DisallowUpdateInsideThisActivity(SR.MovingActivitiesInStateBlockDU);
|
|
return;
|
|
}
|
|
|
|
int originalTriggerInUpdatedDefinition = 0;
|
|
|
|
foreach (InternalTransition originalTransition in originalInternalState.internalTransitions)
|
|
{
|
|
if (metadata.IsReferenceToImportedChild(originalTransition.Trigger))
|
|
{
|
|
metadata.DisallowUpdateInsideThisActivity(SR.TriggerOrConditionIsReferenced);
|
|
return;
|
|
}
|
|
|
|
if (!originalTransition.IsUnconditional)
|
|
{
|
|
// new Trigger activity
|
|
foreach (TransitionData transitionData in originalTransition.TransitionDataList)
|
|
{
|
|
if (metadata.IsReferenceToImportedChild(transitionData.Condition))
|
|
{
|
|
metadata.DisallowUpdateInsideThisActivity(SR.TriggerOrConditionIsReferenced);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (InternalTransition updatedTransition in this.internalTransitions)
|
|
{
|
|
if (metadata.IsReferenceToImportedChild(updatedTransition.Trigger))
|
|
{
|
|
// if the trigger is referenced, it might have another save values already.
|
|
metadata.DisallowUpdateInsideThisActivity(SR.TriggerOrConditionIsReferenced);
|
|
return;
|
|
}
|
|
|
|
Activity triggerMatch = metadata.GetMatch(updatedTransition.Trigger);
|
|
|
|
if (null != triggerMatch)
|
|
{
|
|
InternalTransition originalTransition;
|
|
|
|
if (originalInternalState.triggerInternalTransitionMapping.TryGetValue(triggerMatch, out originalTransition))
|
|
{
|
|
originalTriggerInUpdatedDefinition++;
|
|
|
|
if (originalTransition.IsUnconditional)
|
|
{
|
|
string errorMessage;
|
|
bool canTransitionBeUpdated = ValidateDUInUnconditionalTransition(metadata, updatedTransition, originalTransition, out errorMessage);
|
|
|
|
if (!canTransitionBeUpdated)
|
|
{
|
|
metadata.DisallowUpdateInsideThisActivity(errorMessage);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (updatedTransition.IsUnconditional)
|
|
{
|
|
// cannot change the transition from condition to unconditional.
|
|
metadata.DisallowUpdateInsideThisActivity(SR.ChangeConditionalTransitionToUnconditionalBlockDU);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
string errorMessage;
|
|
bool canTransitionBeUpdated = ValidateDUInConditionTransition(metadata, updatedTransition, originalTransition, out errorMessage);
|
|
|
|
if (!canTransitionBeUpdated)
|
|
{
|
|
metadata.DisallowUpdateInsideThisActivity(errorMessage);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the trigger is an child activity moved from elsewhere within the state
|
|
metadata.DisallowUpdateInsideThisActivity(SR.MovingActivitiesInStateBlockDU);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// new Trigger activity
|
|
foreach (TransitionData transitionData in updatedTransition.TransitionDataList)
|
|
{
|
|
if ((null != transitionData.Condition && null != metadata.GetMatch(transitionData.Condition)) ||
|
|
(null != transitionData.Action && null != metadata.GetMatch(transitionData.Action)))
|
|
{
|
|
// if a new transition is added, it is expected that the Condition/Action
|
|
// are newly created.
|
|
metadata.DisallowUpdateInsideThisActivity(SR.ChangingTriggerOrUseOriginalConditionActionBlockDU);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (originalTriggerInUpdatedDefinition != originalInternalState.internalTransitions.Count)
|
|
{
|
|
// NOTE: in general, if the transition is removed when there are pending triggers,
|
|
// runtime would be able to detect the missing child activities. However, in cases,
|
|
// where the transition is happening already (in between completion of Transition.Action
|
|
// callback but before InternalState is completed), the workflow definition can be unloaded
|
|
// and updated. The InternalState is unable to trace the original transition that set the
|
|
// destination state index. In that case, the update would fail at UpdateInstance.
|
|
// To simplify the model, it is more convenient to disallow removing existing transitions
|
|
// from an executing InternalState. The only extra restriction it brings, is that it disables
|
|
// update even if the InternalState is uploaded at State.Entry. This scenario, however, is uncommon.
|
|
metadata.DisallowUpdateInsideThisActivity(SR.RemovingTransitionsBlockDU);
|
|
}
|
|
}
|
|
|
|
protected override void UpdateInstance(NativeActivityUpdateContext updateContext)
|
|
{
|
|
StateMachineEventManager eventManager = updateContext.GetValue(this.EventManager) as StateMachineEventManager;
|
|
Fx.Assert(eventManager != null, "eventManager is available in every internalActivity.");
|
|
|
|
if (eventManager.CurrentBeingProcessedEvent != null || eventManager.Queue.Any())
|
|
{
|
|
// Updated state is evaluating conditions or transitioning to another state,
|
|
// Then we need to update the index of the current evaluated trigger (in case the trigger is moved)
|
|
// and the condition index.
|
|
// if the state is transitioning already, then we should update destination state id.
|
|
bool isUpdateSuccessful = this.UpdateEventManager(updateContext, eventManager);
|
|
|
|
if (!isUpdateSuccessful)
|
|
{
|
|
updateContext.DisallowUpdate(SR.DUTriggerOrConditionChangedDuringTransitioning);
|
|
return;
|
|
}
|
|
|
|
if (updateContext.GetValue(this.isExiting) != true)
|
|
{
|
|
this.RescheduleNewlyAddedTriggers(updateContext);
|
|
}
|
|
}
|
|
else if (updateContext.GetValue(this.currentRunningTriggers) > 0)
|
|
{
|
|
Fx.Assert(updateContext.GetValue(this.isExiting) != true, "No triggers have completed, state should not be transitioning.");
|
|
|
|
// the state is not transitioning yet and is persisted at trigger.
|
|
this.RescheduleNewlyAddedTriggers(updateContext);
|
|
}
|
|
}
|
|
|
|
static void AddTransitionData(NativeActivityMetadata metadata, InternalTransition internalTransition, Transition transition)
|
|
{
|
|
TransitionData transitionData = new TransitionData();
|
|
Activity<bool> condition = transition.Condition;
|
|
transitionData.Condition = condition;
|
|
|
|
if (condition != null)
|
|
{
|
|
metadata.AddChild(condition);
|
|
}
|
|
|
|
Activity action = transition.Action;
|
|
transitionData.Action = action;
|
|
|
|
if (action != null)
|
|
{
|
|
metadata.AddChild(action);
|
|
}
|
|
|
|
if (transition.To != null)
|
|
{
|
|
transitionData.To = transition.To.InternalState;
|
|
}
|
|
|
|
internalTransition.TransitionDataList.Add(transitionData);
|
|
}
|
|
|
|
static void ProcessNextTriggerCompletedEvent(NativeActivityContext context, StateMachineEventManager eventManager)
|
|
{
|
|
eventManager.CurrentBeingProcessedEvent = null;
|
|
eventManager.OnTransition = false;
|
|
|
|
TriggerCompletedEvent completedEvent = eventManager.GetNextCompletedEvent();
|
|
|
|
if (completedEvent != null)
|
|
{
|
|
StateMachineExtension extension = context.GetExtension<StateMachineExtension>();
|
|
Fx.Assert(extension != null, "Failed to obtain a StateMachineExtension.");
|
|
extension.ResumeBookmark(completedEvent.Bookmark);
|
|
}
|
|
}
|
|
|
|
private static bool ValidateDUInConditionTransition(NativeActivityUpdateMapMetadata metadata, InternalTransition updatedTransition, InternalTransition originalTransition, out string errorMessage)
|
|
{
|
|
Fx.Assert(!originalTransition.IsUnconditional, "Transition should be conditional in the original definition.");
|
|
errorMessage = string.Empty;
|
|
|
|
foreach (TransitionData updatedTData in updatedTransition.TransitionDataList)
|
|
{
|
|
if (metadata.IsReferenceToImportedChild(updatedTData.Condition))
|
|
{
|
|
// if the trigger is referenced, it might have another save values already.
|
|
errorMessage = SR.TriggerOrConditionIsReferenced;
|
|
return false;
|
|
}
|
|
|
|
Fx.Assert(null != updatedTData.Condition, "Must be a condition transition.");
|
|
Activity conditionMatch = metadata.GetMatch(updatedTData.Condition);
|
|
|
|
if (null == conditionMatch && null != metadata.GetMatch(updatedTData.Action))
|
|
{
|
|
// new Transition.Condition with an Transition.Action moved from within the InternalState.
|
|
errorMessage = SR.MovingActivitiesInStateBlockDU;
|
|
return false;
|
|
}
|
|
else if (null != conditionMatch)
|
|
{
|
|
bool foundMatchingOriginalCondition = false;
|
|
|
|
for (int transitionIndex = 0; transitionIndex < originalTransition.TransitionDataList.Count; transitionIndex++)
|
|
{
|
|
if (object.ReferenceEquals(originalTransition.TransitionDataList[transitionIndex].Condition, conditionMatch))
|
|
{
|
|
foundMatchingOriginalCondition = true;
|
|
|
|
// found the original matching condition in updated transition definition.
|
|
TransitionData originalTData = originalTransition.TransitionDataList[transitionIndex];
|
|
|
|
Activity originalAction = originalTData.Action;
|
|
|
|
// NOTE: Transition.Action is allowed to be removed, because it doesn't change the execution semantics of SM
|
|
// if this removed activity was executing, WF runtime would disallow the update.
|
|
Activity actionMatch = metadata.GetMatch(updatedTData.Action);
|
|
|
|
if (null != actionMatch && !object.ReferenceEquals(originalAction, actionMatch))
|
|
{
|
|
// Transition.Action is an activity moved from elsewhere within the InternalState
|
|
errorMessage = SR.MovingActivitiesInStateBlockDU;
|
|
return false;
|
|
}
|
|
|
|
metadata.SaveOriginalValue(updatedTransition.Trigger, originalTransition.InternalTransitionIndex);
|
|
metadata.SaveOriginalValue(updatedTData.Condition, transitionIndex);
|
|
}
|
|
}
|
|
|
|
if (!foundMatchingOriginalCondition)
|
|
{
|
|
// another child activity is move to the Transition.Condition.
|
|
errorMessage = SR.DUDisallowIfCannotFindingMatchingCondition;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool ValidateDUInUnconditionalTransition(NativeActivityUpdateMapMetadata metadata, InternalTransition updatedTransition, InternalTransition originalTransition, out string errorMessage)
|
|
{
|
|
Fx.Assert(originalTransition.IsUnconditional, "Transition should be unconditional in the original definition.");
|
|
Activity originalAction = originalTransition.TransitionDataList[0].Action;
|
|
|
|
foreach (TransitionData transitionData in updatedTransition.TransitionDataList)
|
|
{
|
|
Activity updatedAction = transitionData.Action;
|
|
Activity actionMatch = metadata.GetMatch(updatedAction);
|
|
Activity conditionMatch = metadata.GetMatch(transitionData.Condition);
|
|
|
|
if ((null == originalAction && null != actionMatch) ||
|
|
(null != originalAction && null != actionMatch && !object.ReferenceEquals(originalAction, actionMatch)))
|
|
{
|
|
// Transition.Action is an activity moved from elsewhere within the InternalState
|
|
errorMessage = SR.MovingActivitiesInStateBlockDU;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
errorMessage = string.Empty;
|
|
metadata.SaveOriginalValue(updatedTransition.Trigger, originalTransition.InternalTransitionIndex);
|
|
return true;
|
|
}
|
|
|
|
private void RescheduleNewlyAddedTriggers(NativeActivityUpdateContext updateContext)
|
|
{
|
|
// NOTE: triggers are scheduled already, so the state has completed executing State.Entry
|
|
Fx.Assert(this.internalTransitions.Count == this.triggerInternalTransitionMapping.Count, "Triggers mappings are correct.");
|
|
List<Activity> newTriggers = new List<Activity>();
|
|
|
|
foreach (InternalTransition transition in this.internalTransitions)
|
|
{
|
|
if (updateContext.IsNewlyAdded(transition.Trigger))
|
|
{
|
|
newTriggers.Add(transition.Trigger);
|
|
}
|
|
|
|
// NOTE: all Triggers in triggerInternalTransitionMapping are either new or was previously scheduled
|
|
}
|
|
|
|
foreach (Activity newTrigger in newTriggers)
|
|
{
|
|
updateContext.ScheduleActivity(newTrigger, this.onTriggerComplete);
|
|
}
|
|
|
|
updateContext.SetValue<int>(this.currentRunningTriggers, updateContext.GetValue(this.currentRunningTriggers) + newTriggers.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for Dynamic Update: after the instance is updated, if the statemachine is already transitioning, the index of the to-be-scheduled state
|
|
/// would need to be updated.
|
|
/// </summary>
|
|
/// <param name="updateContext">Dynamic Update context</param>
|
|
/// <param name="eventManager">Internal StateMachineEventManager</param>
|
|
/// <returns>True, 1. if update is successful and the instanced is updated with the new indexes, and 2 all the trigger ID in the queue are updated;
|
|
/// false otherwise and the update should fail.</returns>
|
|
private bool UpdateEventManager(
|
|
NativeActivityUpdateContext updateContext,
|
|
StateMachineEventManager eventManager)
|
|
{
|
|
Fx.Assert(null != eventManager.CurrentBeingProcessedEvent, "The eventManager must have some info that needs to be updated during transition.");
|
|
|
|
int updatedEventsInQueue = 0;
|
|
int originalTriggerId = int.MinValue;
|
|
int originalConditionIndex = int.MinValue;
|
|
bool updateCurrentEventSucceed = null == eventManager.CurrentBeingProcessedEvent ? true : false;
|
|
|
|
foreach (InternalTransition transition in this.internalTransitions)
|
|
{
|
|
object savedTriggerIndex = updateContext.GetSavedOriginalValue(transition.Trigger);
|
|
if (savedTriggerIndex != null)
|
|
{
|
|
Fx.Assert(!updateContext.IsNewlyAdded(transition.Trigger), "the trigger in transition already exist.");
|
|
|
|
if (null != eventManager.CurrentBeingProcessedEvent &&
|
|
eventManager.CurrentBeingProcessedEvent.TriggedId == (int)savedTriggerIndex)
|
|
{
|
|
// found a match of the running trigger update the current processed event
|
|
// Don't match the trigger ID, match only when the Condition is also matched.
|
|
if (eventManager.CurrentConditionIndex == -1)
|
|
{
|
|
if (transition.IsUnconditional)
|
|
{
|
|
// executing transition before persist is unconditional
|
|
originalTriggerId = eventManager.CurrentBeingProcessedEvent.TriggedId;
|
|
originalConditionIndex = 0;
|
|
eventManager.CurrentBeingProcessedEvent.TriggedId = transition.InternalTransitionIndex;
|
|
|
|
if (updateContext.GetValue(this.isExiting))
|
|
{
|
|
Fx.Assert(eventManager.OnTransition, "The state is transitioning.");
|
|
updateContext.SetValue(this.Result, GetTo(transition.InternalTransitionIndex));
|
|
}
|
|
|
|
updateCurrentEventSucceed = true;
|
|
}
|
|
else
|
|
{
|
|
updateContext.DisallowUpdate(SR.ChangeTransitionTypeDuringTransitioningBlockDU);
|
|
return false;
|
|
}
|
|
}
|
|
else if (eventManager.CurrentConditionIndex >= 0)
|
|
{
|
|
Fx.Assert(!transition.IsUnconditional, "Cannot update a running conditional transition with a unconditional one.");
|
|
|
|
if (!transition.IsUnconditional)
|
|
{
|
|
// executing transition before and after are conditional
|
|
for (int updatedIndex = 0; updatedIndex < transition.TransitionDataList.Count; updatedIndex++)
|
|
{
|
|
Activity condition = transition.TransitionDataList[updatedIndex].Condition;
|
|
Fx.Assert(null != condition, "Conditional transition must have Condition activity.");
|
|
int? savedCondIndex = updateContext.GetSavedOriginalValue(condition) as int?;
|
|
|
|
if (eventManager.CurrentConditionIndex == savedCondIndex)
|
|
{
|
|
originalTriggerId = eventManager.CurrentBeingProcessedEvent.TriggedId;
|
|
originalConditionIndex = eventManager.CurrentConditionIndex;
|
|
eventManager.CurrentBeingProcessedEvent.TriggedId = transition.InternalTransitionIndex;
|
|
eventManager.CurrentConditionIndex = updatedIndex;
|
|
|
|
if (updateContext.GetValue(this.isExiting))
|
|
{
|
|
Fx.Assert(eventManager.OnTransition, "The state is transitioning.");
|
|
updateContext.SetValue(this.Result, this.GetTo(transition.InternalTransitionIndex, (int)updatedIndex));
|
|
}
|
|
|
|
updateCurrentEventSucceed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (TriggerCompletedEvent completedEvent in eventManager.Queue)
|
|
{
|
|
if ((int)savedTriggerIndex == completedEvent.TriggedId)
|
|
{
|
|
completedEvent.TriggedId = transition.InternalTransitionIndex;
|
|
updatedEventsInQueue++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return eventManager.Queue.Count() == updatedEventsInQueue ? updateCurrentEventSucceed : false;
|
|
}
|
|
|
|
void ScheduleEntry(NativeActivityContext context)
|
|
{
|
|
context.Track(new StateMachineStateRecord
|
|
{
|
|
StateMachineName = this.StateMachineName,
|
|
StateName = this.DisplayName,
|
|
});
|
|
|
|
if (this.Entry != null)
|
|
{
|
|
context.ScheduleActivity(this.Entry, this.onEntryComplete);
|
|
}
|
|
else
|
|
{
|
|
this.onEntryComplete(context, null);
|
|
}
|
|
}
|
|
|
|
void OnEntryComplete(NativeActivityContext context, ActivityInstance instance)
|
|
{
|
|
ProcessNextTriggerCompletedEvent(context, this.EventManager.Get(context));
|
|
this.ScheduleTriggers(context);
|
|
}
|
|
|
|
void ScheduleTriggers(NativeActivityContext context)
|
|
{
|
|
if (!this.IsFinal)
|
|
{
|
|
// Final state need not condition evaluation bookmark.
|
|
this.AddEvaluateConditionBookmark(context);
|
|
}
|
|
|
|
if (this.internalTransitions.Count > 0)
|
|
{
|
|
foreach (InternalTransition transition in this.internalTransitions)
|
|
{
|
|
context.ScheduleActivity(transition.Trigger, this.onTriggerComplete);
|
|
}
|
|
|
|
this.currentRunningTriggers.Set(context, this.currentRunningTriggers.Get(context) + this.internalTransitions.Count);
|
|
}
|
|
}
|
|
|
|
void OnTriggerComplete(NativeActivityContext context, ActivityInstance completedInstance)
|
|
{
|
|
int runningTriggers = this.currentRunningTriggers.Get(context);
|
|
this.currentRunningTriggers.Set(context, --runningTriggers);
|
|
bool isOnExit = this.isExiting.Get(context);
|
|
|
|
if (!context.IsCancellationRequested && runningTriggers == 0 && isOnExit)
|
|
{
|
|
this.ScheduleExit(context);
|
|
}
|
|
else if (completedInstance.State == ActivityInstanceState.Closed)
|
|
{
|
|
InternalTransition internalTransition = null;
|
|
this.triggerInternalTransitionMapping.TryGetValue(completedInstance.Activity, out internalTransition);
|
|
Fx.Assert(internalTransition != null, "internalTransition should be added into triggerInternalTransitionMapping in CacheMetadata.");
|
|
|
|
StateMachineEventManager eventManager = this.EventManager.Get(context);
|
|
bool canBeProcessedImmediately;
|
|
eventManager.RegisterCompletedEvent(
|
|
new TriggerCompletedEvent { Bookmark = this.evaluateConditionBookmark.Get(context), TriggedId = internalTransition.InternalTransitionIndex },
|
|
out canBeProcessedImmediately);
|
|
|
|
if (canBeProcessedImmediately)
|
|
{
|
|
ProcessNextTriggerCompletedEvent(context, eventManager);
|
|
}
|
|
}
|
|
}
|
|
|
|
void StartEvaluateCondition(NativeActivityContext context, Bookmark bookmark, object value)
|
|
{
|
|
// Start to evaluate conditions of the trigger which represented by currentTriggerIndex
|
|
StateMachineEventManager eventManager = this.EventManager.Get(context);
|
|
int triggerId = eventManager.CurrentBeingProcessedEvent.TriggedId;
|
|
InternalTransition transition = this.GetInternalTransition(triggerId);
|
|
|
|
if (transition.IsUnconditional)
|
|
{
|
|
eventManager.CurrentConditionIndex = -1;
|
|
this.TakeTransition(context, eventManager, triggerId);
|
|
}
|
|
else
|
|
{
|
|
eventManager.CurrentConditionIndex = 0;
|
|
context.ScheduleActivity<bool>(
|
|
this.GetCondition(
|
|
triggerId,
|
|
eventManager.CurrentConditionIndex),
|
|
this.onConditionComplete,
|
|
null);
|
|
}
|
|
}
|
|
|
|
void OnConditionComplete(NativeActivityContext context, ActivityInstance completedInstance, bool result)
|
|
{
|
|
StateMachineEventManager eventManager = this.EventManager.Get(context);
|
|
int triggerId = eventManager.CurrentBeingProcessedEvent.TriggedId;
|
|
|
|
if (result)
|
|
{
|
|
this.TakeTransition(context, eventManager, triggerId);
|
|
}
|
|
else
|
|
{
|
|
// condition failed: reschedule trigger
|
|
int currentConditionIndex = eventManager.CurrentConditionIndex;
|
|
Fx.Assert(eventManager.CurrentConditionIndex >= 0, "Conditional Transition must have non-negative index.");
|
|
InternalTransition transition = this.GetInternalTransition(triggerId);
|
|
currentConditionIndex++;
|
|
|
|
if (currentConditionIndex < transition.TransitionDataList.Count)
|
|
{
|
|
eventManager.CurrentConditionIndex = currentConditionIndex;
|
|
context.ScheduleActivity<bool>(transition.TransitionDataList[currentConditionIndex].Condition, this.onConditionComplete, null);
|
|
}
|
|
else
|
|
{
|
|
// Schedule current trigger again firstly.
|
|
context.ScheduleActivity(transition.Trigger, this.onTriggerComplete);
|
|
this.currentRunningTriggers.Set(context, this.currentRunningTriggers.Get(context) + 1);
|
|
|
|
// check whether there is any other trigger completed.
|
|
ProcessNextTriggerCompletedEvent(context, eventManager);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScheduleExit(NativeActivityContext context)
|
|
{
|
|
if (this.Exit != null)
|
|
{
|
|
context.ScheduleActivity(this.Exit, this.onExitComplete);
|
|
}
|
|
else
|
|
{
|
|
this.onExitComplete(context, null);
|
|
}
|
|
}
|
|
|
|
void OnExitComplete(NativeActivityContext context, ActivityInstance instance)
|
|
{
|
|
this.ScheduleAction(context);
|
|
}
|
|
|
|
void ScheduleAction(NativeActivityContext context)
|
|
{
|
|
StateMachineEventManager eventManager = this.EventManager.Get(context);
|
|
if (eventManager.IsReferredByBeingProcessedEvent(this.evaluateConditionBookmark.Get(context)))
|
|
{
|
|
InternalTransition transition = this.GetInternalTransition(eventManager.CurrentBeingProcessedEvent.TriggedId);
|
|
Activity action = transition.TransitionDataList[-1 == eventManager.CurrentConditionIndex ? 0 : eventManager.CurrentConditionIndex].Action;
|
|
|
|
if (action != null)
|
|
{
|
|
context.ScheduleActivity(action);
|
|
}
|
|
}
|
|
|
|
this.RemoveBookmarks(context);
|
|
}
|
|
|
|
void ProcessTransitions(NativeActivityMetadata metadata)
|
|
{
|
|
for (int i = 0; i < this.Transitions.Count; i++)
|
|
{
|
|
Transition transition = this.Transitions[i];
|
|
InternalTransition internalTransition = null;
|
|
Activity triggerActivity = transition.ActiveTrigger;
|
|
|
|
if (!this.triggerInternalTransitionMapping.TryGetValue(triggerActivity, out internalTransition))
|
|
{
|
|
metadata.AddChild(triggerActivity);
|
|
|
|
internalTransition = new InternalTransition
|
|
{
|
|
Trigger = triggerActivity,
|
|
InternalTransitionIndex = this.internalTransitions.Count,
|
|
};
|
|
|
|
this.triggerInternalTransitionMapping.Add(triggerActivity, internalTransition);
|
|
this.internalTransitions.Add(internalTransition);
|
|
}
|
|
|
|
AddTransitionData(metadata, internalTransition, transition);
|
|
}
|
|
}
|
|
|
|
InternalTransition GetInternalTransition(int triggerIndex)
|
|
{
|
|
return this.internalTransitions[triggerIndex];
|
|
}
|
|
|
|
Activity<bool> GetCondition(int triggerIndex, int conditionIndex)
|
|
{
|
|
return this.internalTransitions[triggerIndex].TransitionDataList[conditionIndex].Condition;
|
|
}
|
|
|
|
string GetTo(int triggerIndex, int conditionIndex = 0)
|
|
{
|
|
return this.internalTransitions[triggerIndex].TransitionDataList[conditionIndex].To.StateId;
|
|
}
|
|
|
|
void AddEvaluateConditionBookmark(NativeActivityContext context)
|
|
{
|
|
Bookmark bookmark = context.CreateBookmark(this.evaluateConditionCallback, BookmarkOptions.MultipleResume);
|
|
this.evaluateConditionBookmark.Set(context, bookmark);
|
|
this.EventManager.Get(context).AddActiveBookmark(bookmark);
|
|
}
|
|
|
|
void RemoveBookmarks(NativeActivityContext context)
|
|
{
|
|
context.RemoveAllBookmarks();
|
|
this.RemoveActiveBookmark(context);
|
|
}
|
|
|
|
void RemoveActiveBookmark(ActivityContext context)
|
|
{
|
|
StateMachineEventManager eventManager = this.EventManager.Get(context);
|
|
Bookmark bookmark = this.evaluateConditionBookmark.Get(context);
|
|
if (bookmark != null)
|
|
{
|
|
eventManager.RemoveActiveBookmark(bookmark);
|
|
}
|
|
}
|
|
|
|
void TakeTransition(NativeActivityContext context, StateMachineEventManager eventManager, int triggerId)
|
|
{
|
|
this.EventManager.Get(context).OnTransition = true;
|
|
InternalTransition transition = this.GetInternalTransition(triggerId);
|
|
|
|
if (transition.IsUnconditional)
|
|
{
|
|
Fx.Assert(-1 == eventManager.CurrentConditionIndex, "CurrentConditionIndex should be -1, if the transition is unconditional.");
|
|
this.PrepareForExit(context, this.GetTo(triggerId));
|
|
}
|
|
else
|
|
{
|
|
Fx.Assert(-1 != eventManager.CurrentConditionIndex, "CurrentConditionIndex should not be -1, if the transition is conditional.");
|
|
this.PrepareForExit(context, this.GetTo(triggerId, eventManager.CurrentConditionIndex));
|
|
}
|
|
}
|
|
|
|
void PrepareForExit(NativeActivityContext context, string targetStateId)
|
|
{
|
|
ReadOnlyCollection<ActivityInstance> children = context.GetChildren();
|
|
this.Result.Set(context, targetStateId);
|
|
this.isExiting.Set(context, true);
|
|
|
|
if (children.Count > 0)
|
|
{
|
|
// Cancel all other pending triggers.
|
|
context.CancelChildren();
|
|
}
|
|
else
|
|
{
|
|
this.ScheduleExit(context);
|
|
}
|
|
}
|
|
}
|
|
}
|