1+ import logging
12from dataclasses import dataclass
23from itertools import chain
34from queue import PriorityQueue
45from queue import Queue
56from threading import Lock
67from typing import TYPE_CHECKING
8+ from typing import Any
79from typing import Callable
10+ from typing import Dict
811from typing import List
12+ from typing import cast
913from weakref import proxy
1014
1115from ..event import BoundEvent
2024if TYPE_CHECKING :
2125 from ..statemachine import StateMachine
2226
27+ logger = logging .getLogger (__name__ )
28+
2329
2430@dataclass (frozen = True , unsafe_hash = True )
2531class StateTransition :
@@ -76,7 +82,7 @@ def empty(self):
7682 def put (self , trigger_data : TriggerData , internal : bool = False ):
7783 """Put the trigger on the queue without blocking the caller."""
7884 if not self .running and not self .sm .allow_event_without_transition :
79- raise TransitionNotAllowed (trigger_data .event , self .sm .current_state )
85+ raise TransitionNotAllowed (trigger_data .event , self .sm .configuration )
8086
8187 if internal :
8288 self .internal_queue .put (trigger_data )
@@ -117,7 +123,9 @@ def _conditions_match(self, transition: Transition, trigger_data: TriggerData):
117123 self .sm ._callbacks .call (transition .validators .key , * args , ** kwargs )
118124 return self .sm ._callbacks .all (transition .cond .key , * args , ** kwargs )
119125
120- def _filter_conflicting_transitions (self , transitions : OrderedSet [Transition ]):
126+ def _filter_conflicting_transitions (
127+ self , transitions : OrderedSet [Transition ]
128+ ) -> OrderedSet [Transition ]:
121129 """
122130 Remove transições conflitantes, priorizando aquelas com estados de origem descendentes
123131 ou que aparecem antes na ordem do documento.
@@ -128,18 +136,18 @@ def _filter_conflicting_transitions(self, transitions: OrderedSet[Transition]):
128136 Returns:
129137 OrderedSet[Transition]: Conjunto de transições sem conflitos.
130138 """
131- filtered_transitions = OrderedSet ()
139+ filtered_transitions = OrderedSet [ Transition ] ()
132140
133141 # Ordena as transições na ordem dos estados que as selecionaram
134142 for t1 in transitions :
135143 t1_preempted = False
136- transitions_to_remove = OrderedSet ()
144+ transitions_to_remove = OrderedSet [ Transition ] ()
137145
138146 # Verifica conflitos com as transições já filtradas
139147 for t2 in filtered_transitions :
140148 # Calcula os conjuntos de saída (exit sets)
141- t1_exit_set = self ._compute_exit_set (t1 )
142- t2_exit_set = self ._compute_exit_set (t2 )
149+ t1_exit_set = self ._compute_exit_set ([ t1 ] )
150+ t2_exit_set = self ._compute_exit_set ([ t2 ] )
143151
144152 # Verifica interseção dos conjuntos de saída
145153 if t1_exit_set & t2_exit_set : # Há interseção
@@ -162,7 +170,7 @@ def _filter_conflicting_transitions(self, transitions: OrderedSet[Transition]):
162170 def _compute_exit_set (self , transitions : List [Transition ]) -> OrderedSet [StateTransition ]:
163171 """Compute the exit set for a transition."""
164172
165- states_to_exit = OrderedSet ()
173+ states_to_exit = OrderedSet [ StateTransition ] ()
166174
167175 for transition in transitions :
168176 if transition .target is None :
@@ -193,6 +201,8 @@ def get_transition_domain(self, transition: Transition) -> "State | None":
193201 and all (state .is_descendant (transition .source ) for state in states )
194202 ):
195203 return transition .source
204+ elif transition .internal and transition .is_self and transition .target .is_atomic :
205+ return transition .source
196206 else :
197207 return self .find_lcca ([transition .source ] + list (states ))
198208
@@ -213,6 +223,7 @@ def find_lcca(states: List[State]) -> "State | None":
213223 ancestors = [anc for anc in head .ancestors () if anc .is_compound ]
214224
215225 # Find the first ancestor that is also an ancestor of all other states in the list
226+ ancestor : State
216227 for ancestor in ancestors :
217228 if all (state .is_descendant (ancestor ) for state in tail ):
218229 return ancestor
@@ -233,13 +244,16 @@ def _select_transitions(
233244 self , trigger_data : TriggerData , predicate : Callable
234245 ) -> OrderedSet [Transition ]:
235246 """Select the transitions that match the trigger data."""
236- enabled_transitions = OrderedSet ()
247+ enabled_transitions = OrderedSet [ Transition ] ()
237248
238249 # Get atomic states, TODO: sorted by document order
239250 atomic_states = (state for state in self .sm .configuration if state .is_atomic )
240251
241- def first_transition_that_matches (state : State , event : Event ) -> "Transition | None" :
252+ def first_transition_that_matches (
253+ state : State , event : "Event | None"
254+ ) -> "Transition | None" :
242255 for s in chain ([state ], state .ancestors ()):
256+ transition : Transition
243257 for transition in s .transitions :
244258 if (
245259 not transition .initial
@@ -248,6 +262,8 @@ def first_transition_that_matches(state: State, event: Event) -> "Transition | N
248262 ):
249263 return transition
250264
265+ return None
266+
251267 for state in atomic_states :
252268 transition = first_transition_that_matches (state , trigger_data .event )
253269 if transition is not None :
@@ -264,6 +280,7 @@ def microstep(self, transitions: List[Transition], trigger_data: TriggerData):
264280 )
265281
266282 states_to_exit = self ._exit_states (transitions , trigger_data )
283+ logger .debug ("States to exit: %s" , states_to_exit )
267284 result += self ._execute_transition_content (transitions , trigger_data , lambda t : t .on .key )
268285 self ._enter_states (transitions , trigger_data , states_to_exit )
269286 self ._execute_transition_content (
@@ -304,7 +321,7 @@ def _exit_states(self, enabled_transitions: List[Transition], trigger_data: Trig
304321 # self.history_values[history.id] = history_value
305322
306323 # Execute `onexit` handlers
307- if info .source is not None and not info .transition .internal :
324+ if info .source is not None : # TODO: and not info.transition.internal:
308325 self .sm ._callbacks .call (info .source .exit .key , * args , ** kwargs )
309326
310327 # TODO: Cancel invocations
@@ -343,22 +360,29 @@ def _enter_states(
343360 """Enter the states as determined by the given transitions."""
344361 states_to_enter = OrderedSet [StateTransition ]()
345362 states_for_default_entry = OrderedSet [StateTransition ]()
346- default_history_content = {}
363+ default_history_content : Dict [ str , Any ] = {}
347364
348365 # Compute the set of states to enter
349366 self .compute_entry_set (
350367 enabled_transitions , states_to_enter , states_for_default_entry , default_history_content
351368 )
352369
353370 # We update the configuration atomically
354- states_targets_to_enter = OrderedSet (info .target for info in states_to_enter )
371+ states_targets_to_enter = OrderedSet (
372+ info .target for info in states_to_enter if info .target
373+ )
374+ logger .debug ("States to enter: %s" , states_targets_to_enter )
375+
355376 configuration = self .sm .configuration
356- self .sm .configuration = (configuration - states_to_exit ) | states_targets_to_enter
377+ self .sm .configuration = cast (
378+ OrderedSet [State ], (configuration - states_to_exit ) | states_targets_to_enter
379+ )
357380
358381 # Sort states to enter in entry order
359382 # for state in sorted(states_to_enter, key=self.entry_order): # TODO: ordegin of states_to_enter # noqa: E501
360383 for info in states_to_enter :
361384 target = info .target
385+ assert target
362386 transition = info .transition
363387 event_data = EventData (trigger_data = trigger_data , transition = transition )
364388 event_data .state = target
@@ -376,8 +400,8 @@ def _enter_states(
376400 # state.is_first_entry = False
377401
378402 # Execute `onentry` handlers
379- if not transition .internal :
380- self .sm ._callbacks .call (target .enter .key , * args , ** kwargs )
403+ # TODO: if not transition.internal:
404+ self .sm ._callbacks .call (target .enter .key , * args , ** kwargs )
381405
382406 # Handle default initial states
383407 # TODO: Handle default initial states
@@ -396,11 +420,17 @@ def _enter_states(
396420 parent = target .parent
397421 grandparent = parent .parent
398422
399- self .internal_queue .put (BoundEvent (f"done.state.{ parent .id } " , _sm = self .sm ))
423+ self .internal_queue .put (
424+ BoundEvent (f"done.state.{ parent .id } " , _sm = self .sm ).build_trigger (
425+ machine = self .sm
426+ )
427+ )
400428 if grandparent .parallel :
401429 if all (child .final for child in grandparent .states ):
402430 self .internal_queue .put (
403- BoundEvent (f"done.state.{ parent .id } " , _sm = self .sm )
431+ BoundEvent (f"done.state.{ parent .id } " , _sm = self .sm ).build_trigger (
432+ machine = self .sm
433+ )
404434 )
405435
406436 def compute_entry_set (
@@ -476,8 +506,19 @@ def add_descendant_states_to_enter(
476506 # return
477507
478508 # Add the state to the entry set
479- states_to_enter .add (info )
509+ if (
510+ not self .sm .enable_self_transition_entries
511+ and info .transition .internal
512+ and (
513+ info .transition .is_self
514+ or info .transition .target .is_descendant (info .transition .source )
515+ )
516+ ):
517+ pass
518+ else :
519+ states_to_enter .add (info )
480520 state = info .target
521+ assert state
481522
482523 if state .is_compound :
483524 # Handle compound states
@@ -541,6 +582,7 @@ def add_ancestor_states_to_enter(
541582 default_history_content: A dictionary to hold temporary content for history states.
542583 """
543584 state = info .target
585+ assert state
544586 for anc in state .ancestors (parent = ancestor ):
545587 # Add the ancestor to the entry set
546588 info_to_add = StateTransition (
0 commit comments