@@ -146,15 +146,23 @@ private bool TryFireInternal(
146146 return true ;
147147 }
148148
149- var targetState = transition . HasTargetState ? transition . TargetState ! : currentState ;
150- targetState = ResolveInitialLeaf ( targetState ) ;
151-
152149 var commandList = new List < TCommand > ( ) ;
153- var isStateChange = transition . HasTargetState
150+ var updatedData = ApplyTransitionSteps (
151+ commandList ,
152+ transition . Steps ,
153+ currentState ,
154+ currentData ,
155+ trigger ,
156+ out var conditionalHasTargetState ,
157+ out var conditionalTargetState ) ;
158+ var hasTargetState = transition . HasTargetState || conditionalHasTargetState ;
159+ var targetState = transition . HasTargetState
160+ ? transition . TargetState !
161+ : conditionalHasTargetState ? conditionalTargetState : currentState ;
162+ targetState = ResolveInitialLeaf ( targetState ) ;
163+ var isStateChange = hasTargetState
154164 && ! EqualityComparer < TState > . Default . Equals ( currentState , targetState ) ;
155165
156- var updatedData = ApplyTransitionSteps ( commandList , transition , currentState , currentData , trigger ) ;
157-
158166 if ( isStateChange )
159167 {
160168 AppendExitCommands ( commandList , currentState , targetState , updatedData ) ;
@@ -260,12 +268,21 @@ internal void Validate(bool skipAnalysis = false)
260268 {
261269 foreach ( var transition in definition . GetTransitions ( ) )
262270 {
263- if ( ! transition . HasTargetState )
271+ if ( ! transition . HasTargetState && ! HasTransitionTargetSteps ( transition . Steps ) )
264272 {
265273 continue ;
266274 }
267275
268- if ( ! _states . ContainsKey ( transition . TargetState ! ) )
276+ foreach ( var targetState in GetTransitionTargetStates ( transition . Steps ) )
277+ {
278+ if ( ! _states . ContainsKey ( targetState ) )
279+ {
280+ throw new InvalidOperationException (
281+ $ "State '{ definition . State } ' transitions to '{ targetState } ', but it is not configured.") ;
282+ }
283+ }
284+
285+ if ( transition . HasTargetState && ! _states . ContainsKey ( transition . TargetState ! ) )
269286 {
270287 throw new InvalidOperationException (
271288 $ "State '{ definition . State } ' transitions to '{ transition . TargetState } ', but it is not configured.") ;
@@ -538,6 +555,41 @@ private List<TState> GetStatesToRoot(TState start)
538555 return states ;
539556 }
540557
558+ private static bool HasTransitionTargetSteps ( List < TransitionStep > steps )
559+ {
560+ return GetTransitionTargetStates ( steps ) . Count > 0 ;
561+ }
562+
563+ private static HashSet < TState > GetTransitionTargetStates ( List < TransitionStep > steps )
564+ {
565+ var targets = new HashSet < TState > ( ) ;
566+ CollectTransitionTargetStates ( steps , targets ) ;
567+ return targets ;
568+ }
569+
570+ private static void CollectTransitionTargetStates ( List < TransitionStep > steps , HashSet < TState > targets )
571+ {
572+ foreach ( var step in steps )
573+ {
574+ switch ( step . Kind )
575+ {
576+ case TransitionStepKind . Transition :
577+ targets . Add ( step . TargetState ! ) ;
578+ break ;
579+ case TransitionStepKind . Conditional :
580+ CollectTransitionTargetStates ( step . ConditionalTrueSteps ! , targets ) ;
581+ CollectTransitionTargetStates ( step . ConditionalFalseSteps ! , targets ) ;
582+ break ;
583+ case TransitionStepKind . ConditionalChain :
584+ foreach ( var branch in step . ConditionalBranches ! )
585+ {
586+ CollectTransitionTargetStates ( branch . Steps , targets ) ;
587+ }
588+ break ;
589+ }
590+ }
591+ }
592+
541593 private bool TryFindLowestCommonAncestor ( TState currentState , TState targetState , out TState lca )
542594 {
543595 var currentChain = GetHierarchyChain ( currentState ) ;
@@ -635,7 +687,7 @@ private static void AppendCommands(
635687 }
636688
637689 var targetState = ResolveInitialLeaf ( matched . TargetState ! ) ;
638- var updatedData = ApplyTransitionSteps ( commands , matched , state , data , trigger ) ;
690+ var updatedData = ApplyTransitionSteps ( commands , matched . Steps , state , data , trigger , out _ , out _ ) ;
639691 var isStateChange = ! EqualityComparer < TState > . Default . Equals ( state , targetState ) ;
640692
641693 if ( isStateChange )
@@ -652,23 +704,17 @@ private static void AppendCommands(
652704 $ "Immediate transition loop detected starting at '{ currentState } '.") ;
653705 }
654706
655- private static TData ApplyTransitionSteps (
656- List < TCommand > commands ,
657- TransitionDefinition transition ,
658- TState currentState ,
659- TData currentData ,
660- TTrigger trigger )
661- {
662- return ApplyTransitionSteps ( commands , transition . Steps , currentState , currentData , trigger ) ;
663- }
664-
665707 private static TData ApplyTransitionSteps (
666708 List < TCommand > commands ,
667709 List < TransitionStep > steps ,
668710 TState currentState ,
669711 TData currentData ,
670- TTrigger trigger )
712+ TTrigger trigger ,
713+ out bool hasTargetState ,
714+ out TState targetState )
671715 {
716+ hasTargetState = false ;
717+ targetState = default ! ;
672718 var updatedData = currentData ;
673719 foreach ( var step in steps )
674720 {
@@ -683,18 +729,46 @@ private static TData ApplyTransitionSteps(
683729 commands . Add ( command ) ;
684730 }
685731 break ;
732+ case TransitionStepKind . Transition :
733+ hasTargetState = true ;
734+ targetState = step . TargetState ! ;
735+ break ;
686736 case TransitionStepKind . Conditional :
687737 var branch = step . Predicate ! ( currentState , updatedData , trigger )
688738 ? step . ConditionalTrueSteps !
689739 : step . ConditionalFalseSteps ! ;
690- updatedData = ApplyTransitionSteps ( commands , branch , currentState , updatedData , trigger ) ;
740+ updatedData = ApplyTransitionSteps (
741+ commands ,
742+ branch ,
743+ currentState ,
744+ updatedData ,
745+ trigger ,
746+ out var conditionalHasTargetState ,
747+ out var conditionalTargetState ) ;
748+ if ( conditionalHasTargetState && ! hasTargetState )
749+ {
750+ hasTargetState = true ;
751+ targetState = conditionalTargetState ;
752+ }
691753 break ;
692754 case TransitionStepKind . ConditionalChain :
693755 var matchedBranch = step . ConditionalBranches ! . FirstOrDefault ( b =>
694756 b . Predicate ( currentState , updatedData , trigger ) ) ;
695757 if ( matchedBranch . Steps != null )
696758 {
697- updatedData = ApplyTransitionSteps ( commands , matchedBranch . Steps , currentState , updatedData , trigger ) ;
759+ updatedData = ApplyTransitionSteps (
760+ commands ,
761+ matchedBranch . Steps ,
762+ currentState ,
763+ updatedData ,
764+ trigger ,
765+ out var chainHasTargetState ,
766+ out var chainTargetState ) ;
767+ if ( chainHasTargetState && ! hasTargetState )
768+ {
769+ hasTargetState = true ;
770+ targetState = chainTargetState ;
771+ }
698772 }
699773 break ;
700774 }
@@ -1439,6 +1513,12 @@ public ConditionalTransitionConfiguration Execute(Func<IEnumerable<TCommand>> ac
14391513 return Execute ( ( state , data , trigger ) => action ( ) ) ;
14401514 }
14411515
1516+ public ConditionalTransitionConfiguration TransitionTo ( TState state )
1517+ {
1518+ _currentBranchSteps ! . Add ( TransitionStep . ForTransition ( state ) ) ;
1519+ return this ;
1520+ }
1521+
14421522 public ConditionalTransitionConfiguration ElseIf ( Func < TData , TTrigger , bool > predicate )
14431523 {
14441524 _branches . Add ( ( ( state , data , trigger ) => predicate ( data , trigger ) , [ ] ) ) ;
@@ -1604,6 +1684,12 @@ public ConditionalTransitionConfiguration<TDerivedTrigger> Execute(Func<IEnumera
16041684 return Execute ( ( state , data , trigger ) => action ( ) ) ;
16051685 }
16061686
1687+ public ConditionalTransitionConfiguration < TDerivedTrigger > TransitionTo ( TState state )
1688+ {
1689+ _currentBranchSteps ! . Add ( TransitionStep . ForTransition ( state ) ) ;
1690+ return this ;
1691+ }
1692+
16071693 public ConditionalTransitionConfiguration < TDerivedTrigger > ElseIf (
16081694 Func < TData , TDerivedTrigger , bool > predicate )
16091695 {
@@ -1736,6 +1822,7 @@ internal enum TransitionStepKind
17361822 {
17371823 ModifyData ,
17381824 Execute ,
1825+ Transition ,
17391826 Conditional ,
17401827 ConditionalChain
17411828 }
@@ -1755,6 +1842,8 @@ private TransitionStep(TransitionStepKind kind)
17551842
17561843 public Func < TState , TData , TTrigger , bool > ? Predicate { get ; private init ; }
17571844
1845+ public TState ? TargetState { get ; private init ; }
1846+
17581847 public List < TransitionStep > ? ConditionalTrueSteps { get ; private init ; }
17591848
17601849 public List < TransitionStep > ? ConditionalFalseSteps { get ; private init ; }
@@ -1771,6 +1860,11 @@ public static TransitionStep ForExecute(Func<TState, TData, TTrigger, IEnumerabl
17711860 return new TransitionStep ( TransitionStepKind . Execute ) { Executor = action } ;
17721861 }
17731862
1863+ public static TransitionStep ForTransition ( TState targetState )
1864+ {
1865+ return new TransitionStep ( TransitionStepKind . Transition ) { TargetState = targetState } ;
1866+ }
1867+
17741868 public static TransitionStep ForConditional (
17751869 Func < TState , TData , TTrigger , bool > predicate ,
17761870 List < TransitionStep > trueSteps ,
@@ -2362,6 +2456,12 @@ public ConditionalTransitionConfiguration Execute(Func<IEnumerable<TCommand>> ac
23622456 return this ;
23632457 }
23642458
2459+ public ConditionalTransitionConfiguration TransitionTo ( TState state )
2460+ {
2461+ _inner . TransitionTo ( state ) ;
2462+ return this ;
2463+ }
2464+
23652465 public ConditionalTransitionConfiguration Else ( )
23662466 {
23672467 _inner . Else ( ) ;
@@ -2455,6 +2555,12 @@ public ConditionalTransitionConfiguration<TDerivedTrigger> Execute(Func<IEnumera
24552555 return this ;
24562556 }
24572557
2558+ public ConditionalTransitionConfiguration < TDerivedTrigger > TransitionTo ( TState state )
2559+ {
2560+ _inner . TransitionTo ( state ) ;
2561+ return this ;
2562+ }
2563+
24582564 public ConditionalTransitionConfiguration < TDerivedTrigger > Else ( )
24592565 {
24602566 _inner . Else ( ) ;
0 commit comments