//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ 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; /// /// InternalState is internal representation of State. /// sealed class InternalState : NativeActivity { // State denotes corresponding State object. State state; // internal representation of transitions. Collection internalTransitions; // number of running triggers Variable currentRunningTriggers; Variable isExiting; // This bookmark is used to evaluate condition of a transition of this state. Variable 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 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 triggerInternalTransitionMapping = new Dictionary(); 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(this.OnConditionComplete); this.onExitComplete = new CompletionCallback(this.OnExitComplete); this.evaluateConditionCallback = new BookmarkCallback(this.StartEvaluateCondition); this.currentRunningTriggers = new Variable(); this.isExiting = new Variable(); this.evaluateConditionBookmark = new Variable(); this.internalTransitions = new Collection(); this.triggerInternalTransitionMapping = new Dictionary(); } /// /// Gets or sets EventManager is used to globally manage event queue such that triggered events can be processed in order. /// [RequiredArgument] public InArgument EventManager { get; set; } /// /// Gets Entry activity that will be executed when state is entering. /// public Activity Entry { get { return this.state.Entry; } } /// /// Gets Exit activity that will be executed when state is leaving. /// public Activity Exit { get { return this.state.Exit; } } /// /// Gets a value indicating whether this state is a final state or not. /// [DefaultValue(false)] public bool IsFinal { get { return this.state.IsFinal; } } /// /// Gets StateId, which is the identifier of a state. It's unique within a StateMachine. /// public string StateId { get { return this.state.StateId; } } /// /// Gets Transitions collection contains transitions on this state. /// public Collection Transitions { get { return this.state.Transitions; } } /// /// Gets Variables collection contains Variables on this state. /// public Collection Variables { get { return this.state.Variables; } } /// /// Gets the display name of the parent state machine of the state. /// Used for tracking purpose only. /// 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 { 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 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(); 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 foundMatchin----ginalCondition = false; for (int transitionIndex = 0; transitionIndex < originalTransition.TransitionDataList.Count; transitionIndex++) { if (object.ReferenceEquals(originalTransition.TransitionDataList[transitionIndex].Condition, conditionMatch)) { foundMatchin----ginalCondition = 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 (!foundMatchin----ginalCondition) { // 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 newTriggers = new List(); 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(this.currentRunningTriggers, updateContext.GetValue(this.currentRunningTriggers) + newTriggers.Count); } /// /// 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. /// /// Dynamic Update context /// Internal StateMachineEventManager /// 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. 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( 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(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 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 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); } } } }