ultra-smart-sys 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/state_machines-0.201.0/LICENSE.txt +23 -0
- data/state_machines-0.201.0/README.md +1024 -0
- data/state_machines-0.201.0/lib/state_machines/async_mode/async_event_extensions.rb +49 -0
- data/state_machines-0.201.0/lib/state_machines/async_mode/async_events.rb +282 -0
- data/state_machines-0.201.0/lib/state_machines/async_mode/async_machine.rb +60 -0
- data/state_machines-0.201.0/lib/state_machines/async_mode/async_transition_collection.rb +136 -0
- data/state_machines-0.201.0/lib/state_machines/async_mode/thread_safe_state.rb +47 -0
- data/state_machines-0.201.0/lib/state_machines/async_mode.rb +64 -0
- data/state_machines-0.201.0/lib/state_machines/branch.rb +246 -0
- data/state_machines-0.201.0/lib/state_machines/callback.rb +223 -0
- data/state_machines-0.201.0/lib/state_machines/core.rb +43 -0
- data/state_machines-0.201.0/lib/state_machines/core_ext/class/state_machine.rb +5 -0
- data/state_machines-0.201.0/lib/state_machines/core_ext.rb +4 -0
- data/state_machines-0.201.0/lib/state_machines/error.rb +115 -0
- data/state_machines-0.201.0/lib/state_machines/eval_helpers.rb +227 -0
- data/state_machines-0.201.0/lib/state_machines/event.rb +247 -0
- data/state_machines-0.201.0/lib/state_machines/event_collection.rb +149 -0
- data/state_machines-0.201.0/lib/state_machines/extensions.rb +150 -0
- data/state_machines-0.201.0/lib/state_machines/helper_module.rb +19 -0
- data/state_machines-0.201.0/lib/state_machines/integrations/base.rb +49 -0
- data/state_machines-0.201.0/lib/state_machines/integrations.rb +111 -0
- data/state_machines-0.201.0/lib/state_machines/machine/action_hooks.rb +53 -0
- data/state_machines-0.201.0/lib/state_machines/machine/async_extensions.rb +88 -0
- data/state_machines-0.201.0/lib/state_machines/machine/callbacks.rb +333 -0
- data/state_machines-0.201.0/lib/state_machines/machine/class_methods.rb +95 -0
- data/state_machines-0.201.0/lib/state_machines/machine/configuration.rb +128 -0
- data/state_machines-0.201.0/lib/state_machines/machine/event_methods.rb +436 -0
- data/state_machines-0.201.0/lib/state_machines/machine/helper_generators.rb +125 -0
- data/state_machines-0.201.0/lib/state_machines/machine/integration.rb +92 -0
- data/state_machines-0.201.0/lib/state_machines/machine/parsing.rb +77 -0
- data/state_machines-0.201.0/lib/state_machines/machine/rendering.rb +17 -0
- data/state_machines-0.201.0/lib/state_machines/machine/scoping.rb +44 -0
- data/state_machines-0.201.0/lib/state_machines/machine/state_methods.rb +398 -0
- data/state_machines-0.201.0/lib/state_machines/machine/utilities.rb +86 -0
- data/state_machines-0.201.0/lib/state_machines/machine/validation.rb +39 -0
- data/state_machines-0.201.0/lib/state_machines/machine.rb +615 -0
- data/state_machines-0.201.0/lib/state_machines/machine_collection.rb +105 -0
- data/state_machines-0.201.0/lib/state_machines/macro_methods.rb +522 -0
- data/state_machines-0.201.0/lib/state_machines/matcher.rb +124 -0
- data/state_machines-0.201.0/lib/state_machines/matcher_helpers.rb +56 -0
- data/state_machines-0.201.0/lib/state_machines/node_collection.rb +226 -0
- data/state_machines-0.201.0/lib/state_machines/options_validator.rb +72 -0
- data/state_machines-0.201.0/lib/state_machines/path.rb +123 -0
- data/state_machines-0.201.0/lib/state_machines/path_collection.rb +91 -0
- data/state_machines-0.201.0/lib/state_machines/state.rb +314 -0
- data/state_machines-0.201.0/lib/state_machines/state_collection.rb +113 -0
- data/state_machines-0.201.0/lib/state_machines/state_context.rb +134 -0
- data/state_machines-0.201.0/lib/state_machines/stdio_renderer.rb +74 -0
- data/state_machines-0.201.0/lib/state_machines/syntax_validator.rb +54 -0
- data/state_machines-0.201.0/lib/state_machines/test_helper.rb +775 -0
- data/state_machines-0.201.0/lib/state_machines/transition.rb +593 -0
- data/state_machines-0.201.0/lib/state_machines/transition_collection.rb +310 -0
- data/state_machines-0.201.0/lib/state_machines/version.rb +5 -0
- data/state_machines-0.201.0/lib/state_machines.rb +6 -0
- data/ultra-smart-sys.gemspec +12 -0
- metadata +96 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StateMachines
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def self.extended(base) # :nodoc:
|
|
6
|
+
base.class_eval do
|
|
7
|
+
@state_machines = MachineCollection.new
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Gets the current list of state machines defined for this class. This
|
|
12
|
+
# class-level attribute acts like an inheritable attribute. The attribute
|
|
13
|
+
# is available to each subclass, each having a copy of its superclass's
|
|
14
|
+
# attribute.
|
|
15
|
+
#
|
|
16
|
+
# The hash of state machines maps <tt>:attribute</tt> => +machine+, e.g.
|
|
17
|
+
#
|
|
18
|
+
# Vehicle.state_machines # => {:state => #<StateMachines::Machine:0xb6f6e4a4 ...>}
|
|
19
|
+
def state_machines
|
|
20
|
+
@state_machines ||= superclass.state_machines.dup
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module InstanceMethods
|
|
25
|
+
# Runs one or more events in parallel. All events will run through the
|
|
26
|
+
# following steps:
|
|
27
|
+
# * Before callbacks
|
|
28
|
+
# * Persist state
|
|
29
|
+
# * Invoke action
|
|
30
|
+
# * After callbacks
|
|
31
|
+
#
|
|
32
|
+
# For example, if two events (for state machines A and B) are run in
|
|
33
|
+
# parallel, the order in which steps are run is:
|
|
34
|
+
# * A - Before transition callbacks
|
|
35
|
+
# * B - Before transition callbacks
|
|
36
|
+
# * A - Persist new state
|
|
37
|
+
# * B - Persist new state
|
|
38
|
+
# * A - Invoke action
|
|
39
|
+
# * B - Invoke action (only if different than A's action)
|
|
40
|
+
# * A - After transition callbacks
|
|
41
|
+
# * B - After transition callbacks
|
|
42
|
+
#
|
|
43
|
+
# *Note* that multiple events on the same state machine / attribute cannot
|
|
44
|
+
# be run in parallel. If this is attempted, an ArgumentError will be
|
|
45
|
+
# raised.
|
|
46
|
+
#
|
|
47
|
+
# == Halting callbacks
|
|
48
|
+
#
|
|
49
|
+
# When running multiple events in parallel, special consideration should
|
|
50
|
+
# be taken with regard to how halting within callbacks affects the flow.
|
|
51
|
+
#
|
|
52
|
+
# For *before* callbacks, any <tt>:halt</tt> error that's thrown will
|
|
53
|
+
# immediately cancel the perform for all transitions. As a result, it's
|
|
54
|
+
# possible for one event's transition to affect the continuation of
|
|
55
|
+
# another.
|
|
56
|
+
#
|
|
57
|
+
# On the other hand, any <tt>:halt</tt> error that's thrown within an
|
|
58
|
+
# *after* callback with only affect that event's transition. Other
|
|
59
|
+
# transitions will continue to run their own callbacks.
|
|
60
|
+
#
|
|
61
|
+
# == Example
|
|
62
|
+
#
|
|
63
|
+
# class Vehicle
|
|
64
|
+
# state_machine :initial => :parked do
|
|
65
|
+
# event :ignite do
|
|
66
|
+
# transition :parked => :idling
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# event :park do
|
|
70
|
+
# transition :idling => :parked
|
|
71
|
+
# end
|
|
72
|
+
# end
|
|
73
|
+
#
|
|
74
|
+
# state_machine :alarm_state, :namespace => 'alarm', :initial => :on do
|
|
75
|
+
# event :enable do
|
|
76
|
+
# transition all => :active
|
|
77
|
+
# end
|
|
78
|
+
#
|
|
79
|
+
# event :disable do
|
|
80
|
+
# transition all => :off
|
|
81
|
+
# end
|
|
82
|
+
# end
|
|
83
|
+
# end
|
|
84
|
+
#
|
|
85
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c02850 @state="parked", @alarm_state="active">
|
|
86
|
+
# vehicle.state # => "parked"
|
|
87
|
+
# vehicle.alarm_state # => "active"
|
|
88
|
+
#
|
|
89
|
+
# vehicle.fire_events(:ignite, :disable_alarm) # => true
|
|
90
|
+
# vehicle.state # => "idling"
|
|
91
|
+
# vehicle.alarm_state # => "off"
|
|
92
|
+
#
|
|
93
|
+
# # If any event fails, the entire event chain fails
|
|
94
|
+
# vehicle.fire_events(:ignite, :enable_alarm) # => false
|
|
95
|
+
# vehicle.state # => "idling"
|
|
96
|
+
# vehicle.alarm_state # => "off"
|
|
97
|
+
#
|
|
98
|
+
# # Exception raised on invalid event
|
|
99
|
+
# vehicle.fire_events(:park, :invalid) # => StateMachines::InvalidEvent: :invalid is an unknown event
|
|
100
|
+
# vehicle.state # => "idling"
|
|
101
|
+
# vehicle.alarm_state # => "off"
|
|
102
|
+
def fire_events(*events)
|
|
103
|
+
self.class.state_machines.fire_events(self, *events)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Run one or more events in parallel. If any event fails to run, then
|
|
107
|
+
# a StateMachines::InvalidTransition exception will be raised.
|
|
108
|
+
#
|
|
109
|
+
# See StateMachines::InstanceMethods#fire_events for more information.
|
|
110
|
+
#
|
|
111
|
+
# == Example
|
|
112
|
+
#
|
|
113
|
+
# class Vehicle
|
|
114
|
+
# state_machine :initial => :parked do
|
|
115
|
+
# event :ignite do
|
|
116
|
+
# transition :parked => :idling
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
119
|
+
# event :park do
|
|
120
|
+
# transition :idling => :parked
|
|
121
|
+
# end
|
|
122
|
+
# end
|
|
123
|
+
#
|
|
124
|
+
# state_machine :alarm_state, :namespace => 'alarm', :initial => :active do
|
|
125
|
+
# event :enable do
|
|
126
|
+
# transition all => :active
|
|
127
|
+
# end
|
|
128
|
+
#
|
|
129
|
+
# event :disable do
|
|
130
|
+
# transition all => :off
|
|
131
|
+
# end
|
|
132
|
+
# end
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c02850 @state="parked", @alarm_state="active">
|
|
136
|
+
# vehicle.fire_events(:ignite, :disable_alarm) # => true
|
|
137
|
+
#
|
|
138
|
+
# vehicle.fire_events!(:ignite, :disable_alarm) # => StateMachines::InvalidParallelTransition: Cannot run events in parallel: ignite, disable_alarm
|
|
139
|
+
def fire_events!(*events)
|
|
140
|
+
run_action = [true, false].include?(events.last) ? events.pop : true
|
|
141
|
+
fire_events(*(events + [run_action])) || raise(StateMachines::InvalidParallelTransition.new(self, events))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
protected
|
|
145
|
+
|
|
146
|
+
def initialize_state_machines(options = {}, &) # :nodoc:
|
|
147
|
+
self.class.state_machines.initialize_states(self, options, &)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StateMachines
|
|
4
|
+
# Represents a type of module that defines instance / class methods for a
|
|
5
|
+
# state machine
|
|
6
|
+
class HelperModule < Module # :nodoc:
|
|
7
|
+
def initialize(machine, kind)
|
|
8
|
+
@machine = machine
|
|
9
|
+
@kind = kind
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Provides a human-readable description of the module
|
|
13
|
+
def to_s
|
|
14
|
+
owner_class = @machine.owner_class
|
|
15
|
+
owner_class_name = owner_class.name && !owner_class.name.empty? ? owner_class.name : owner_class.to_s
|
|
16
|
+
"#{owner_class_name} #{@machine.name.inspect} #{@kind} helpers"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StateMachines
|
|
4
|
+
module Integrations
|
|
5
|
+
# Provides a set of base helpers for managing individual integrations
|
|
6
|
+
module Base
|
|
7
|
+
module ClassMethods
|
|
8
|
+
# The default options to use for state machines using this integration
|
|
9
|
+
attr_reader :defaults
|
|
10
|
+
|
|
11
|
+
# The name of the integration
|
|
12
|
+
def integration_name
|
|
13
|
+
@integration_name ||= begin
|
|
14
|
+
name = self.name.split('::').last
|
|
15
|
+
name.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
16
|
+
name.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
|
17
|
+
name.downcase!
|
|
18
|
+
name.to_sym
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The list of ancestor names that cause this integration to matched.
|
|
23
|
+
def matching_ancestors
|
|
24
|
+
[]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Whether the integration should be used for the given class.
|
|
28
|
+
def matches?(klass)
|
|
29
|
+
matching_ancestors.any? { |ancestor| klass <= ancestor }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether the integration should be used for the given list of ancestors.
|
|
33
|
+
def matches_ancestors?(ancestors)
|
|
34
|
+
(ancestors & matching_ancestors).any?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Additional options that this integration adds to the state machine.
|
|
38
|
+
# Integrations can override this method to specify additional valid options.
|
|
39
|
+
def integration_options
|
|
40
|
+
[]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.included(base) # :nodoc:
|
|
45
|
+
base.extend ClassMethods
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StateMachines
|
|
4
|
+
# Integrations allow state machines to take advantage of features within the
|
|
5
|
+
# context of a particular library. This is currently most useful with
|
|
6
|
+
# database libraries. For example, the various database integrations allow
|
|
7
|
+
# state machines to hook into features like:
|
|
8
|
+
# * Saving
|
|
9
|
+
# * Transactions
|
|
10
|
+
# * Observers
|
|
11
|
+
# * Scopes
|
|
12
|
+
# * Callbacks
|
|
13
|
+
# * Validation errors
|
|
14
|
+
#
|
|
15
|
+
# This type of integration allows the user to work with state machines in a
|
|
16
|
+
# fashion similar to other object models in their application.
|
|
17
|
+
#
|
|
18
|
+
# The integration interface is loosely defined by various unimplemented
|
|
19
|
+
# methods in the StateMachines::Machine class. See that class or the various
|
|
20
|
+
# built-in integrations for more information about how to define additional
|
|
21
|
+
# integrations.
|
|
22
|
+
module Integrations
|
|
23
|
+
@integrations = []
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# Register integration
|
|
27
|
+
def register(name_or_module)
|
|
28
|
+
case name_or_module.class.to_s
|
|
29
|
+
when 'Module'
|
|
30
|
+
add(name_or_module)
|
|
31
|
+
else
|
|
32
|
+
raise IntegrationError
|
|
33
|
+
end
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reset # :nodoc:#
|
|
38
|
+
@integrations = []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Gets a list of all of the available integrations for use.
|
|
42
|
+
#
|
|
43
|
+
# == Example
|
|
44
|
+
#
|
|
45
|
+
# StateMachines::Integrations.integrations
|
|
46
|
+
# # => []
|
|
47
|
+
# StateMachines::Integrations.register(StateMachines::Integrations::ActiveModel)
|
|
48
|
+
# StateMachines::Integrations.integrations
|
|
49
|
+
# # => [StateMachines::Integrations::ActiveModel]
|
|
50
|
+
attr_reader :integrations
|
|
51
|
+
|
|
52
|
+
alias list integrations
|
|
53
|
+
|
|
54
|
+
# Attempts to find an integration that matches the given class. This will
|
|
55
|
+
# look through all of the built-in integrations under the StateMachines::Integrations
|
|
56
|
+
# namespace and find one that successfully matches the class.
|
|
57
|
+
#
|
|
58
|
+
# == Examples
|
|
59
|
+
#
|
|
60
|
+
# class Vehicle
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# class ActiveModelVehicle
|
|
64
|
+
# include ActiveModel::Observing
|
|
65
|
+
# include ActiveModel::Validations
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# class ActiveRecordVehicle < ActiveRecord::Base
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# StateMachines::Integrations.match(Vehicle) # => nil
|
|
72
|
+
# StateMachines::Integrations.match(ActiveModelVehicle) # => StateMachines::Integrations::ActiveModel
|
|
73
|
+
# StateMachines::Integrations.match(ActiveRecordVehicle) # => StateMachines::Integrations::ActiveRecord
|
|
74
|
+
def match(klass)
|
|
75
|
+
integrations.detect { |integration| integration.matches?(klass) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Attempts to find an integration that matches the given list of ancestors.
|
|
79
|
+
# This will look through all of the built-in integrations under the StateMachines::Integrations
|
|
80
|
+
# namespace and find one that successfully matches one of the ancestors.
|
|
81
|
+
#
|
|
82
|
+
# == Examples
|
|
83
|
+
#
|
|
84
|
+
# StateMachines::Integrations.match_ancestors([]) # => nil
|
|
85
|
+
# StateMachines::Integrations.match_ancestors([ActiveRecord::Base]) # => StateMachines::Integrations::ActiveModel
|
|
86
|
+
def match_ancestors(ancestors)
|
|
87
|
+
integrations.detect { |integration| integration.matches_ancestors?(ancestors) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Finds an integration with the given name. If the integration cannot be
|
|
91
|
+
# found, then a NameError exception will be raised.
|
|
92
|
+
#
|
|
93
|
+
# == Examples
|
|
94
|
+
#
|
|
95
|
+
# StateMachines::Integrations.find_by_name(:active_model) # => StateMachines::Integrations::ActiveModel
|
|
96
|
+
# StateMachines::Integrations.find_by_name(:active_record) # => StateMachines::Integrations::ActiveRecord
|
|
97
|
+
# StateMachines::Integrations.find_by_name(:invalid) # => StateMachines::IntegrationNotFound: :invalid is an invalid integration
|
|
98
|
+
def find_by_name(name)
|
|
99
|
+
integrations.detect { |integration| integration.integration_name == name } || raise(IntegrationNotFound.new(name))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def add(integration)
|
|
105
|
+
return unless integration.respond_to?(:integration_name)
|
|
106
|
+
|
|
107
|
+
@integrations.insert(0, integration) unless @integrations.include?(integration)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StateMachines
|
|
4
|
+
class Machine
|
|
5
|
+
module ActionHooks
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
# Determines whether action helpers should be defined for this machine.
|
|
9
|
+
# This is only true if there is an action configured and no other machines
|
|
10
|
+
# have process this same configuration already.
|
|
11
|
+
def define_action_helpers?
|
|
12
|
+
action && owner_class.state_machines.none? { |_name, machine| machine.action == action && machine != self }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Adds helper methods for automatically firing events when an action
|
|
16
|
+
# is invoked
|
|
17
|
+
def define_action_helpers
|
|
18
|
+
return unless action_hook
|
|
19
|
+
|
|
20
|
+
@action_hook_defined = true
|
|
21
|
+
define_action_hook
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Hooks directly into actions by defining the same method in an included
|
|
25
|
+
# module. As a result, when the action gets invoked, any state events
|
|
26
|
+
# defined for the object will get run. Method visibility is preserved.
|
|
27
|
+
def define_action_hook
|
|
28
|
+
action_hook = self.action_hook
|
|
29
|
+
action = self.action
|
|
30
|
+
private_action_hook = owner_class.private_method_defined?(action_hook)
|
|
31
|
+
|
|
32
|
+
# Only define helper if it hasn't
|
|
33
|
+
define_helper :instance, <<-END_EVAL, __FILE__, __LINE__ + 1
|
|
34
|
+
def #{action_hook}(*)
|
|
35
|
+
self.class.state_machines.transitions(self, #{action.inspect}).perform { super }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private #{action_hook.inspect} if #{private_action_hook}
|
|
39
|
+
END_EVAL
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The method to hook into for triggering transitions when invoked. By
|
|
43
|
+
# default, this is the action configured for the machine.
|
|
44
|
+
#
|
|
45
|
+
# Since the default hook technique relies on module inheritance, the
|
|
46
|
+
# action must be defined in an ancestor of the owner classs in order for
|
|
47
|
+
# it to be the action hook.
|
|
48
|
+
def action_hook
|
|
49
|
+
action && owner_class_ancestor_has_method?(:instance, action) ? action : nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This file provides optional async extensions for the Machine class.
|
|
4
|
+
# It should only be loaded when async functionality is explicitly requested.
|
|
5
|
+
|
|
6
|
+
module StateMachines
|
|
7
|
+
class Machine
|
|
8
|
+
# AsyncMode extensions for the Machine class
|
|
9
|
+
# Provides async-aware methods while maintaining backward compatibility
|
|
10
|
+
module AsyncExtensions
|
|
11
|
+
# Instance methods added to Machine for async support
|
|
12
|
+
|
|
13
|
+
# Configure this specific machine instance for async mode
|
|
14
|
+
#
|
|
15
|
+
# Example:
|
|
16
|
+
# class Vehicle
|
|
17
|
+
# state_machine initial: :parked do
|
|
18
|
+
# configure_async_mode! # Enable async for this machine
|
|
19
|
+
#
|
|
20
|
+
# event :ignite do
|
|
21
|
+
# transition parked: :idling
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
def configure_async_mode!(enabled = true)
|
|
26
|
+
if enabled
|
|
27
|
+
begin
|
|
28
|
+
require 'state_machines/async_mode'
|
|
29
|
+
@async_mode_enabled = true
|
|
30
|
+
|
|
31
|
+
owner_class.include(StateMachines::AsyncMode::ThreadSafeState)
|
|
32
|
+
owner_class.include(StateMachines::AsyncMode::AsyncEvents)
|
|
33
|
+
extend(StateMachines::AsyncMode::AsyncMachine)
|
|
34
|
+
|
|
35
|
+
# Extend events to generate async versions
|
|
36
|
+
events.each do |event|
|
|
37
|
+
event.extend(StateMachines::AsyncMode::AsyncEventExtensions)
|
|
38
|
+
end
|
|
39
|
+
rescue LoadError => e
|
|
40
|
+
# Fallback to sync mode with warning (only once per class)
|
|
41
|
+
unless owner_class.instance_variable_get(:@async_fallback_warned)
|
|
42
|
+
warn <<~WARNING
|
|
43
|
+
⚠️ #{owner_class.name}: Async mode requested but not available on #{RUBY_ENGINE}.
|
|
44
|
+
|
|
45
|
+
#{e.message}
|
|
46
|
+
|
|
47
|
+
⚠️ Falling back to synchronous mode. Results may be unpredictable due to engine limitations.
|
|
48
|
+
For production async support, use MRI Ruby (CRuby) 3.2+
|
|
49
|
+
WARNING
|
|
50
|
+
owner_class.instance_variable_set(:@async_fallback_warned, true)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@async_mode_enabled = false
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
@async_mode_enabled = false
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Check if this specific machine instance has async mode enabled
|
|
63
|
+
def async_mode_enabled?
|
|
64
|
+
@async_mode_enabled || false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Thread-safe version of state reading
|
|
68
|
+
def read_safely(object, attribute, ivar = false)
|
|
69
|
+
object.read_state_safely(self, attribute, ivar)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Thread-safe version of state writing
|
|
73
|
+
def write_safely(object, attribute, value, ivar = false)
|
|
74
|
+
object.write_state_safely(self, attribute, value, ivar)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Thread-safe callback execution for async operations
|
|
78
|
+
def run_callbacks_safely(type, object, context, transition)
|
|
79
|
+
object.state_machine_mutex.with_write_lock do
|
|
80
|
+
callbacks[type].each { |callback| callback.call(object, context, transition) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Include async extensions by default (but only load AsyncMode when requested)
|
|
86
|
+
include AsyncExtensions
|
|
87
|
+
end
|
|
88
|
+
end
|