Class: Roby::EventGenerator

Inherits:
PlanObject show all
Extended by:
DRoby::Identifiable, DRoby::V5::DRobyConstant::Dump
Includes:
DRoby::V5::EventGeneratorDumper, GUI::RelationsCanvasEventGenerator
Defined in:
lib/roby/event_generator.rb,
lib/roby.rb,
lib/roby/droby/enable.rb

Overview

EventGenerator objects are the objects which manage the event generation process (propagation, event creation, ...). They can be combined logically using & and |.

Standard relations

  • signals: calls the command of an event when this generator emits
  • forwardings: emits another event when this generator emits

Hooks

The following hooks are defined:

  • #calling
  • #called
  • #fired
  • #signalling
  • #forwarding

Defined Under Namespace

Classes: EventHandler

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes inherited from PlanObject

#addition_time, #executable, #execution_engine, #finalization_handlers, #finalization_time, #plan, #promise_executor, #removed_at

Attributes included from Transaction::Proxying::Cache

#transaction_forwarder_module, #transaction_proxy_module

Attributes included from Relations::DirectedRelationSupport

#relation_graphs

Attributes inherited from DistributedObject

#local_owner_id, #owners

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DRoby::Identifiable

droby_id

Methods included from DRoby::V5::DRobyConstant::Dump

droby_dump, droby_marshallable?

Methods included from DRoby::V5::EventGeneratorDumper

#droby_dump

Methods included from GUI::RelationsCanvasEventGenerator

#display, #display_create, #display_name, #display_time_end, #display_time_start, priorities, style, styles

Methods included from GUI::RelationsCanvasPlanObject

#display, #display_create, #display_events, #display_name, #display_parent

Methods inherited from PlanObject

#apply_relation_changes, #as_plan, #can_finalize?, #commit_transaction, #concrete_model, #connection_space, #each_finalization_handler, #each_in_neighbour_merged, #each_out_neighbour_merged, #each_plan_child, #engine, #executable?, #finalized?, #forget_peer, #fullfills?, #garbage?, #merged_relations, #promise, #read_write?, #real_object, #remotely_useful?, #replace_subplan_by, #root_object, #root_object?, #subscribed?, #transaction_proxy?, #transaction_stack, #update_on?, #updated_by?, #when_finalized

Methods included from Models::PlanObject

#child_plan_object, #finalization_handler, #when_finalized

Methods included from GUI::GraphvizPlanObject

#apply_layout, #dot_label, #to_dot

Methods included from Relations::DirectedRelationSupport

#[], #[]=, #add_parent_object, #child_object?, #child_objects, #clear_vertex, #each_child_object, #each_in_neighbour, #each_out_neighbour, #each_parent_object, #each_relation, #each_relation_graph, #each_relation_sorted, #each_root_relation_graph, #enum_child_objects, #enum_parent_objects, #enum_relations, #leaf?, #parent_object?, #parent_objects, #related_object?, #related_objects, #relation_graph_for, #relations, #remove_child_object, #remove_children, #remove_parent_object, #remove_parents, #remove_relations, #root?, #sorted_relations

Methods inherited from DistributedObject

#add_owner, #clear_owners, #owned_by?, #remove_owner

Constructor Details

#initialize(command_object = nil, controlable: false, plan: TemplatePlan.new, &command_block) ⇒ EventGenerator

call-seq:

EventGenerator.new
EventGenerator.new(false)
EventGenerator.new(true)
EventGenerator.new { |event| ... }

Create a new event generator. If a block is given, the event is controlable and the block is its command. If a true argument is given, the event is controlable and is 'pass-through': it is emitted as soon as its command is called. If no argument is given (or a false argument), then it is not controlable



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/roby/event_generator.rb', line 121

def initialize(command_object = nil, controlable: false, plan: TemplatePlan.new, &command_block)
    @preconditions   = []
    @handlers        = []
    @pending         = false
    @pending_sources = []
    @unreachable     = false
    @unreachable_events = {}
    @unreachable_handlers = []
    @history = []
    @event_model = Event

    command_object ||= controlable

    if command_object || command_block
        @command =
            if command_object.respond_to?(:call)
                command_object
            elsif command_block
                command_block
            else
                method(:default_command)
            end
    else
        @command = nil
    end
    super(plan: plan)
    plan.register_event(self)
end

Class Attribute Details

.all_relation_spacesObject (readonly)

Returns the value of attribute all_relation_spaces.



22
23
24
# File 'lib/roby/event_generator.rb', line 22

def all_relation_spaces
  @all_relation_spaces
end

.relation_spacesObject (readonly)

Returns the value of attribute relation_spaces.



22
23
24
# File 'lib/roby/event_generator.rb', line 22

def relation_spaces
  @relation_spaces
end

Instance Attribute Details

#commandObject

The current command block



156
157
158
# File 'lib/roby/event_generator.rb', line 156

def command
  @command
end

#event_modelObject (readonly)

The event class that is used to represent this generator's emissions

Defaults to Event



30
31
32
# File 'lib/roby/event_generator.rb', line 30

def event_model
  @event_model
end

#historyObject (readonly)

A [time, event] array of past event emitted by this object



849
850
851
# File 'lib/roby/event_generator.rb', line 849

def history
  @history
end

#unreachability_reasonObject (readonly)

If the event became unreachable, this holds the reason for its unreachability, if that reason is known.



1013
1014
1015
# File 'lib/roby/event_generator.rb', line 1013

def unreachability_reason
  @unreachability_reason
end

#unreachable_handlersArray<(Boolean,EventHandler)> (readonly)

A set of blocks called when this event cannot be emitted again

Returns:



414
415
416
# File 'lib/roby/event_generator.rb', line 414

def unreachable_handlers
  @unreachable_handlers
end

Class Method Details

.matchObject



1094
1095
1096
# File 'lib/roby/event_generator.rb', line 1094

def self.match
    Queries::EventGeneratorMatcher.new.with_model(self)
end

Instance Method Details

#&(other) ⇒ Object

Creates a AndGenerator object which is emitted when both this object and generator are emitted

See AndGenerator for a complete description.

Note that this operator always creates a new generator, thus

a & b & c & d

will create 3 AndGenerator instances. It is in general better to use & for event pairs, and use AndGenerator#<< when multiple events have to be aggregated:

AndGenerator.new << a << b << c << d



66
67
68
# File 'lib/roby/event_generator.rb', line 66

def &(other)
    AndGenerator.new << self << other
end

#achieve_asynchronously(promise = nil, description: "#{self}#achieve_asynchronously", emit_on_success: true, on_failure: :fail, context: nil, &block) ⇒ Promise

Declares that the command of this event should be achieved by calling the provided block

Parameters:

  • emit_on_success (Boolean) (defaults to: true)

    if true, the event will be emitted if the block got called successfully. Otherwise, nothing will be done.

  • a (Promise)

    promise object that represents the work. Use Roby::ExecutionEngine#promise to create this promise.

  • block (Proc, nil)

    a block from which the method will create a promise. This promise is not returned as it would give a false sense of security.

  • on_failure (Symbol) (defaults to: :fail)

    controls what happens if the promise fails. With the default of :fail, the event generator's emit_failed is called. If it is :emit, it gets emitted. If it is :nothing, nothing's done

Returns:

  • (Promise)

    the promise. Do NOT chain work on this promise, as that work won't be automatically error-checked by Roby's mechanisms



813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
# File 'lib/roby/event_generator.rb', line 813

def achieve_asynchronously(
    promise = nil,
    description: "#{self}#achieve_asynchronously",
    emit_on_success: true, on_failure: :fail, context: nil, &block
)
    if promise && block
        raise ArgumentError, "cannot give both a promise and a block"
    elsif !%i[fail emit nothing].include?(on_failure)
        raise ArgumentError, "expected on_failure to either be :fail or :emit"
    elsif block
        promise = execution_engine.promise(description: description, &block)
    end

    if promise.null?
        emit(*context) if emit_on_success
        return
    end

    if emit_on_success
        promise.on_success(description: "#{self}.emit") { emit(*context) }
    end
    if on_failure != :nothing
        promise.on_error(description: "#{self}#emit_failed") do |reason|
            case on_failure
            when :fail
                emit_failed(reason)
            when :emit
                emit(*context)
            end
        end
    end
    promise.execute
    promise
end

#achieve_with(generator) ⇒ Object #achieve_with(generator) {|event| ... } ⇒ Object

Set this generator up so that it "delegates" its emission to another event

Overloads:

  • #achieve_with(generator) ⇒ Object

    Emit self next time generator is emitted, and mark it as unreachable if generator is. The event context is propagated through.

    Parameters:

  • #achieve_with(generator) {|event| ... } ⇒ Object

    Emit self next time generator is emitted, and mark it as unreachable if generator is. The value returned by the block is used as self's event context

    An exception raised by the filter will be localized on self.

    Parameters:

    Yield Parameters:

    • event (Event)

      the event emitted by 'generator'

    Yield Returns:

    • (Object)

      the context to be used for self's event



768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
# File 'lib/roby/event_generator.rb', line 768

def achieve_with(ev)
    if block_given?
        ev.add_causal_link self
        ev.once do |event|
            begin
                context = yield(event)
                do_emit = true
            rescue Exception => e
                emit_failed(e)
            end
            if do_emit
                self.emit(context)
            end
        end
    else
        ev.forward_to_once self
    end

    ev.if_unreachable(cancel_at_emission: true) do |reason, event|
        emit_failed(EmissionFailed.new(UnreachableEvent.new(ev, reason), self))
    end
end

#add_child_object(child, type, info) ⇒ Object

Checks that ownership allows to add the self => child relation



990
991
992
993
994
995
996
997
998
999
# File 'lib/roby/event_generator.rb', line 990

def add_child_object(child, type, info) # :nodoc:
    unless child.read_write?
        raise OwnershipError,
              "cannot add an event relation on a child we don't own. "\
              "#{child} is owned by #{child.owners.to_a} (plan is "\
              "owned by #{plan.owners.to_a if plan})"
    end

    super
end

#call(*context) ⇒ Object

Call the command associated with self. Note that an event might be non-controlable and respond to the :call message. Controlability must be checked using #controlable?



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/roby/event_generator.rb', line 269

def call(*context)
    engine = execution_engine
    if engine && !engine.in_propagation_context?
        Roby.warn_deprecated "calling EventGenerator#call outside of a "\
                             "propagation context is deprecated. In "\
                             "tests, use execute { } or expect_execution "\
                             "{ }.to { }"
        engine.process_events_synchronous { call(*context) }
        return
    end

    if error = check_call_validity
        clear_pending
        raise error
    end

    # This test must not be done in #emit_without_propagation as the
    # other ones: it is possible, using Distributed.update, to disable
    # ownership tests, but that does not work if the test is in
    # #emit_without_propagation
    unless self_owned?
        raise OwnershipError, "not owner"
    end

    execution_engine.queue_signal(engine.propagation_sources, self, context, nil)
end

#call_handlers(event) ⇒ Object

Call the event handlers defined for this event generator



627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
# File 'lib/roby/event_generator.rb', line 627

def call_handlers(event)
    # Since we are in a gathering context, call
    # to other objects are not done, but gathered in the
    # :propagation TLS
    all_handlers = enum_for(:each_handler).to_a
    processed_once_handlers = all_handlers.find_all do |h|
        begin
            h.call(event)
        rescue LocalizedError => e
            execution_engine.add_error(e)
        rescue Exception => e
            execution_engine.add_error(EventHandlerError.new(e, event.generator))
        end
        h.once?
    end
    handlers.delete_if { |h| processed_once_handlers.include?(h) }
end

#call_unreachable_handlers(reason) ⇒ Object

Internal helper for unreachable!



1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
# File 'lib/roby/event_generator.rb', line 1016

def call_unreachable_handlers(reason) # :nodoc:
    unreachable_handlers.each do |(_, handler)|
        begin
            handler.call(reason, self)
        rescue LocalizedError => e
            execution_engine.add_error(e)
        rescue Exception => e
            execution_engine.add_error(EventHandlerError.new(e, self))
        end
    end
    unreachable_handlers.clear
end

#call_without_propagation(context) ⇒ Object

Calls the command from within the event propagation code



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/roby/event_generator.rb', line 225

def call_without_propagation(context)
    if error = check_call_validity
        clear_pending
        execution_engine.add_error(error)
        return
    end

    calling(context)

    if (error = check_call_validity) || (error = check_call_validity_after_calling)
        clear_pending
        execution_engine.add_error(error)
        return
    end

    begin
        @calling_command = true
        @command_emitted = false
        execution_engine.propagation_context([self]) do
            command.call(context)
        end
    rescue Exception => e
        unless e.kind_of?(LocalizedError)
            e = CommandFailed.new(e, self)
        end
        if command_emitted?
            execution_engine.add_error(e)
        else
            emit_failed(e)
        end
    ensure
        @calling_command = false
    end
    called(context)
end

#called(context) ⇒ Object

Hook called just after the event command has been called



920
# File 'lib/roby/event_generator.rb', line 920

def called(context); end

#calling(context) ⇒ Object

Hook called when this event generator is called (i.e. the associated command is), before the command is actually called. Think of it as a pre-call hook.



904
905
906
907
908
909
910
911
912
913
914
915
916
917
# File 'lib/roby/event_generator.rb', line 904

def calling(context)
    each_precondition do |reason, block|
        result = begin
            block.call(self, context)
        rescue EventPreconditionFailed => e
            e.generator = self
            raise
        end

        unless result
            raise EventPreconditionFailed.new(self), "precondition #{reason} failed"
        end
    end
end

#cancel(reason = nil) ⇒ Object

Call this method in the #calling hook to cancel calling the event command. This raises an EventCanceled exception with reason for message

Raises:



880
881
882
# File 'lib/roby/event_generator.rb', line 880

def cancel(reason = nil)
    raise EventCanceled.new(self), (reason || "event canceled")
end

#check_call_validityObject

Checks that the event can be called. Raises various exception when it is not the case.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/roby/event_generator.rb', line 165

def check_call_validity
    if !plan
        EventNotExecutable.new(self)
            .exception("#emit called on #{self} which has been removed from its plan")
    elsif !plan.executable?
        EventNotExecutable.new(self)
            .exception("#emit called on #{self} which is not in an executable plan")
    elsif !controlable?
        EventNotControlable.new(self)
            .exception("#call called on a non-controlable event")
    elsif unreachable?
        if unreachability_reason
            UnreachableEvent.new(self, unreachability_reason)
                .exception("#call called on #{self} which has been made unreachable because of #{unreachability_reason}")
        else
            UnreachableEvent.new(self, unreachability_reason)
                .exception("#call called on #{self} which has been made unreachable")
        end
    elsif !execution_engine.allow_propagation?
        PhaseMismatch.exception("call to #emit is not allowed in this context")
    elsif !execution_engine.inside_control?
        ThreadMismatch.exception("#call called while not in control thread")
    end
end

#check_call_validity_after_callingObject



190
191
192
193
194
195
# File 'lib/roby/event_generator.rb', line 190

def check_call_validity_after_calling
    unless executable?
        EventNotExecutable.new(self)
            .exception("#call called on #{self} which is a non-executable event")
    end
end

#check_emission_validityObject

Checks that the event can be emitted. Raises various exception when it is not the case.



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/roby/event_generator.rb', line 199

def check_emission_validity
    if !plan
        EventNotExecutable.new(self)
            .exception("#emit called on #{self} which has been removed from its plan")
    elsif !plan.executable?
        EventNotExecutable.new(self)
            .exception("#emit called on #{self} which is not in an executable plan")
    elsif !executable?
        EventNotExecutable.new(self)
            .exception("#emit called on #{self} which is a non-executable event")
    elsif unreachable?
        if unreachability_reason
            UnreachableEvent.new(self, unreachability_reason)
                .exception("#emit called on #{self} which has been made unreachable because of #{unreachability_reason}")
        else
            UnreachableEvent.new(self, unreachability_reason)
                .exception("#emit called on #{self} which has been made unreachable")
        end
    elsif !execution_engine.allow_propagation?
        PhaseMismatch.exception("call to #emit is not allowed in this context")
    elsif !execution_engine.inside_control?
        ThreadMismatch.exception("#emit called while not in control thread")
    end
end

#clear_pendingObject

Method called to clear a pending call

Submodels can hook into this to do some cleanup



896
897
898
899
# File 'lib/roby/event_generator.rb', line 896

def clear_pending
    @pending = false
    @pending_sources = []
end

#controlable?Boolean

True if this event is controlable

Returns:

  • (Boolean)


159
160
161
# File 'lib/roby/event_generator.rb', line 159

def controlable?
    !!@command
end

#create_transaction_proxy(transaction) ⇒ Object



1107
1108
1109
# File 'lib/roby/event_generator.rb', line 1107

def create_transaction_proxy(transaction)
    transaction.create_and_register_proxy_event(self)
end

#default_command(context) ⇒ Object

The default command of emitted the event



151
152
153
# File 'lib/roby/event_generator.rb', line 151

def default_command(context) # :nodoc:
    emit(*context)
end

#delay(seconds) ⇒ Object

Returns an event which is emitted seconds seconds after this one



515
516
517
518
519
520
521
522
# File 'lib/roby/event_generator.rb', line 515

def delay(seconds)
    if seconds == 0 then self
    else
        ev = EventGenerator.new
        forward_to(ev, delay: seconds)
        ev
    end
end

#each_precondition(&block) ⇒ Object

Yields all precondition handlers defined for this generator



873
874
875
# File 'lib/roby/event_generator.rb', line 873

def each_precondition(&block)
    @preconditions.each(&block)
end

#emit(*context) ⇒ Object

Emit the event with context as the event context



718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
# File 'lib/roby/event_generator.rb', line 718

def emit(*context)
    engine = execution_engine
    if engine && !engine.in_propagation_context?
        Roby.warn_deprecated "calling EventGenerator#emit outside of a "\
                             "propagation context is deprecated. In tests, "\
                             "use execute { } or expect_execution { }.to { }"
        engine.process_events_synchronous { emit(*context) }
        return
    end

    if (error = check_emission_validity)
        clear_pending
        raise error
    end

    # This test must not be done in #emit_without_propagation as the
    # other ones: it is possible, using Distributed.update, to disable
    # ownership tests, but that does not work if the test is in
    # #emit_without_propagation
    unless self_owned?
        raise OwnershipError, "cannot emit an event we don't own. #{self} is owned by #{owners}"
    end

    if @calling_command
        @command_emitted = true
    end

    engine.queue_forward(
        engine.propagation_sources, self, context, nil)
end

#emit_failed(error = nil, message = nil) ⇒ Object

Raises an exception object when an event whose command has been called won't be emitted (ever)



647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
# File 'lib/roby/event_generator.rb', line 647

def emit_failed(error = nil, message = nil)
    engine = execution_engine
    if engine && !engine.in_propagation_context?
        Roby.warn_deprecated "calling EventGenerator#emit_failed outside of a "\
                             "propagation context is deprecated. In tests, "\
                             "use execute { } or expect_execution { }.to { }"
        engine.process_events_synchronous { emit_failed(error, message) }
        return
    end

    error ||= EmissionFailed

    if !message && !(error.kind_of?(Class) || error.kind_of?(Exception))
        message = error.to_str
        error = EmissionFailed
    end

    failure_message =
        if message then message
        elsif error.respond_to?(:message) then error.message
        else
            "failed to emit #{self}"
        end

    if Class === error
        error = error.new(nil, self)
        error.set_backtrace caller(1)
    end

    new_error = error.exception failure_message
    new_error.set_backtrace error.backtrace
    error = new_error

    unless error.kind_of?(LocalizedError)
        error = EmissionFailed.new(error, self)
    end

    execution_engine.log(:generator_emit_failed, self, error)
    execution_engine.add_error(error)
    error
ensure
    clear_pending
end

#emit_without_propagation(context) ⇒ Object

Emits the event regardless of wether we are in a propagation context or not. Returns true to match the behavior of #call_without_propagation

This is used by event propagation. Do not call directly: use #call instead



695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
# File 'lib/roby/event_generator.rb', line 695

def emit_without_propagation(context)
    if error = check_emission_validity
        execution_engine.add_error(error)
        return
    end

    emitting(context)

    # Create the event object
    event = new(context)
    unless event.respond_to?(:add_sources)
        raise TypeError, "#{event} is not a valid event object in #{self}"
    end

    event.add_sources(execution_engine.propagation_source_events)
    event.add_sources(@pending_sources)
    fire(event)
    event
ensure
    clear_pending
end

#emitting(context) ⇒ Object

Hook called when this event will be emitted



923
# File 'lib/roby/event_generator.rb', line 923

def emitting(context); end

#filter(*new_context, &block) ⇒ Object

call-seq:

filter(new_context) => filtering_event
filter { |context| ... } => filtering_event

Returns an event generator which forwards the events fired by this one, but by changing the context. In the first form, the new context is set to new_context. In the second form, to the value returned by the given block

Example:

base = task1.intermediate_event
filtered = base.filter(10)

base.on { |base_ev| ... }
filtered.on { |filtered_ev| ... }

base.emit(20)
# base_ev.context is [20]
# filtered_ev.context is [10]

The returned value is a FilterGenerator instance which is the child of self in the signalling relation



956
957
958
959
960
# File 'lib/roby/event_generator.rb', line 956

def filter(*new_context, &block)
    filter = FilterGenerator.new(new_context, &block)
    self.signals(filter)
    filter
end

#finalized!(timestamp = nil) ⇒ Object

Called when the object has been removed from its plan



1002
1003
1004
1005
# File 'lib/roby/event_generator.rb', line 1002

def finalized!(timestamp = nil)
    super
    unreachable_handlers.clear
end

#fire(event) ⇒ Object

Do fire this event. It gathers the list of signals that are to be propagated in the next step and calls fired()

This method is always called in a propagation context



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
# File 'lib/roby/event_generator.rb', line 594

def fire(event)
    @emitted = true
    clear_pending
    fired(event)

    execution_engine = self.execution_engine

    signal_graph = execution_engine.signal_graph
    each_signal do |target|
        if self == target
            raise PropagationError, "#{self} is trying to signal itself"
        end

        execution_engine.queue_signal([event], target, event.context,
                                      signal_graph.edge_info(self, target))
    end

    forward_graph = execution_engine.forward_graph
    each_forwarding do |target|
        if self == target
            raise PropagationError, "#{self} is trying to signal itself"
        end

        execution_engine.queue_forward([event], target, event.context,
                                       forward_graph.edge_info(self, target))
    end

    execution_engine.propagation_context([event]) do
        call_handlers(event)
    end
end

#fired(event) ⇒ Object

Hook called when this generator has been fired. event is the Event object which has been created.



927
928
929
930
931
# File 'lib/roby/event_generator.rb', line 927

def fired(event)
    unreachable_handlers.delete_if { |cancel, _| cancel }
    history << event
    execution_engine.log(:generator_fired, event)
end

#forward(generator, timespec = nil) ⇒ Object



490
491
492
493
# File 'lib/roby/event_generator.rb', line 490

def forward(generator, timespec = nil)
    Roby.warn_deprecated "EventGenerator#forward has been renamed into EventGenerator#forward_to"
    forward_to(generator, timespec)
end

#forward_once(ev, delay = nil) ⇒ Object



542
543
544
545
# File 'lib/roby/event_generator.rb', line 542

def forward_once(ev, delay = nil)
    Roby.warn_deprecated "#forward_once has been renamed into #forward_to_once"
    forward_to_once(ev)
end

#forward_to(generator, timespec = nil) ⇒ Object

Emit generator when self is fired, without calling the command of generator, if any.

If timespec is given it is either a delay: time association, or a at: time association. In the first case, time is a floating-point delay in seconds and in the second case it is a Time object which is the absolute point in time at which this propagation must happen.



502
503
504
505
506
# File 'lib/roby/event_generator.rb', line 502

def forward_to(generator, timespec = nil)
    timespec = ExecutionEngine.validate_timespec(timespec)
    add_forwarding generator, timespec
    self
end

#forward_to_once(ev, delay = nil) ⇒ Object

Forwards to the given target event only once



548
549
550
551
552
553
554
# File 'lib/roby/event_generator.rb', line 548

def forward_to_once(ev, delay = nil)
    forward_to(ev, delay)
    once do |context|
        remove_forwarding ev
    end
    self
end

#forwarded_to?(generator) ⇒ Boolean

Tests whether self is forwarded to the given generator

Returns:

  • (Boolean)


509
510
511
512
# File 'lib/roby/event_generator.rb', line 509

def forwarded_to?(generator)
    plan.event_relation_graph_for(EventStructure::Forwarding)
        .has_edge?(self, generator)
end

#garbage!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Called if the event has been garbage-collected, but cannot be finalized yet (possibly because PlanObject#can_finalize? returns false)



1049
1050
1051
1052
# File 'lib/roby/event_generator.rb', line 1049

def garbage!
    super
    unreachable!
end

#happened?Boolean

True if this event has been emitted once.

Returns:

  • (Boolean)


854
855
856
857
# File 'lib/roby/event_generator.rb', line 854

def happened?
    Roby.warn_deprecated "#happened? is deprecated, use #emitted? instead"
    emitted?
end

#if_unreachable(options = {}) {|reason, generator| ... } ⇒ Object

Calls block if it is impossible that this event is ever emitted

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :cancel_at_emission (Boolean) — default: false

    if true, the block will only be called if the event did not get emitted since the handler got installed.

  • :on_replace (:drop, :copy) — default: :drop

    if set to drop, the block will not be passed to events that replace this one. Otherwise, the block gets copied

Yield Parameters:

  • reason (Object)

    the unreachability reason (usually an exception)

  • generator (EventGenerator)

    the event generator that became unreachable. This is needed when the :on_replace option is :copy, since the generator that became unreachable might be different than the one on which the handler got installed



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/roby/event_generator.rb', line 431

def if_unreachable(options = {}, &block)
    if [true, false].include?(options)
        Roby.warn_deprecated "if_unreachable(cancel_at_emission) has been "\
                             "replaced by if_unreachable(cancel_at_emission: "\
                             "true or false, on_replace: :policy)"
        options = Hash[cancel_at_emission: options]
    end
    options = Kernel.validate_options options,
                                      cancel_at_emission: false,
                                      on_replace: :drop

    unless %i[drop copy].include?(options[:on_replace])
        raise ArgumentError,
              "wrong value for the :on_replace option. "\
              "Expecting either :drop or :copy, got #{options[:on_replace]}"
    end

    check_arity(block, 2)
    if unreachable_handlers.any? { |cancel, b| b.block == block }
        return b.object_id
    end

    handler = EventHandler.new(block, options[:on_replace] == :copy, true)
    unreachable_handlers << [options[:cancel_at_emission], handler]
    handler.object_id
end

#initialize_copy(old) ⇒ Object

:nodoc:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/roby/event_generator.rb', line 72

def initialize_copy(old) # :nodoc:
    super

    @event_model = old.event_model
    @preconditions = old.instance_variable_get(:@preconditions).dup
    @handlers = old.handlers.dup
    @emitted = old.emitted?
    @history = old.history.dup
    if old.command.kind_of?(Method)
        @command = method(old.command.name)
    end
    @unreachable = old.unreachable?
    @unreachable_handlers = old.unreachable_handlers.dup

    @pending = false
    @pending_sources = []
end

#initialize_replacement(event, &block) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/roby/event_generator.rb', line 363

def initialize_replacement(event, &block)
    for h in handlers
        if h.copy_on_replace?
            event ||= yield
            event.on(**h.as_options, &h.block)
        end
    end

    for h in unreachable_handlers
        cancel, h = h
        if h.copy_on_replace?
            event ||= yield
            event.if_unreachable(
                cancel_at_emission: cancel, on_replace: :copy, &h.block
            )
        end
    end

    if event
        super(event)
    else
        super(nil, &block)
    end
end

#lastObject

Last event to have been emitted by this generator



860
861
862
# File 'lib/roby/event_generator.rb', line 860

def last
    history.last
end

#mark_unreachable!(reason) ⇒ Object



1039
1040
1041
1042
1043
# File 'lib/roby/event_generator.rb', line 1039

def mark_unreachable!(reason)
    clear_pending
    @unreachable = true
    @unreachability_reason = reason
end

#matchObject



1098
1099
1100
# File 'lib/roby/event_generator.rb', line 1098

def match
    Queries::EventGeneratorMatcher.new(self)
end

#modelObject

Returns the model object for this particular event generator. It is in general the generator class.



92
93
94
# File 'lib/roby/event_generator.rb', line 92

def model
    self.class
end

#nameObject

The model name



97
98
99
# File 'lib/roby/event_generator.rb', line 97

def name
    model.name
end

#new(context, propagation_id = nil, time = nil) ⇒ Object

Create a new event object for context



585
586
587
588
# File 'lib/roby/event_generator.rb', line 585

def new(context, propagation_id = nil, time = nil) # :nodoc:
    event_model.new(self, propagation_id || execution_engine.propagation_id,
                    context, time || Time.now)
end

#on(on_replace: :drop, once: false, &handler) ⇒ Object

call-seq:

on { |event| ... }
on(on_replace: :forward) { |event| ... }

Adds an event handler on this generator. The block gets an Event object which describes the parameters of the emission (context value, time, ...). See Event for details.

The :on_replace option governs what will happen with this handler if this task is replaced by another.

  • if set to :drop, the handler is not passed on
  • if set to :forward, the handler is added to the replacing task


353
354
355
356
357
358
359
360
361
# File 'lib/roby/event_generator.rb', line 353

def on(on_replace: :drop, once: false, &handler)
    unless %i[drop copy].include?(on_replace)
        raise ArgumentError, "wrong value for the :on_replace option. Expecting either :drop or :copy, got #{on_replace}"
    end

    check_arity(handler, 1)
    self.handlers << EventHandler.new(handler, on_replace == :copy, once)
    self
end

#once(**options, &block) ⇒ Object

call-seq:

once { |context| ... }

Calls the provided event handler only once



537
538
539
540
# File 'lib/roby/event_generator.rb', line 537

def once(**options, &block)
    on(**options.merge(once: true), &block)
    self
end

#pending(sources) ⇒ Object

Method called to declare that a call is pending

Parameters:

  • sources (Array<EventGenerator>)

    list of event generators that are responsible for the call



888
889
890
891
# File 'lib/roby/event_generator.rb', line 888

def pending(sources)
    @pending = true
    @pending_sources.concat(sources)
end

#plan=(plan) ⇒ Object



103
104
105
106
107
108
# File 'lib/roby/event_generator.rb', line 103

def plan=(plan)
    null = self.plan&.null_event_relation_graphs
    super
    @relation_graphs =
        plan&.event_relation_graphs || null || @relation_graphs
end

#precondition(reason = nil, &block) ⇒ Object

Defines a precondition handler for this event. Precondition handlers are blocks which are called just before the event command is called. If the handler returns false, the calling is aborted by a PreconditionFailed exception



868
869
870
# File 'lib/roby/event_generator.rb', line 868

def precondition(reason = nil, &block)
    @preconditions << [reason, block]
end

#pretty_print(pp, context_task: nil) ⇒ Object

:nodoc:



1077
1078
1079
1080
1081
1082
1083
1084
# File 'lib/roby/event_generator.rb', line 1077

def pretty_print(pp, context_task: nil) # :nodoc:
    pp.text to_s
    pp.group(2, " {", "}") do
        pp.breakable
        pp.text "owners: "
        pp.seplist(owners) { |r| pp.text r.to_s }
    end
end

#realize_with(task) ⇒ Object

For backwards compatibility. Use #achieve_with.



792
793
794
# File 'lib/roby/event_generator.rb', line 792

def realize_with(task)
    achieve_with(task)
end

Returns the set of events directly related to this one



561
562
563
# File 'lib/roby/event_generator.rb', line 561

def related_events(result = Set.new)
    related_objects(nil, result)
end

Returns the set of tasks that are directly linked to this events.

I.e. it returns the tasks that have events which are directly related to this event, self.task excluded:

ev = task.intermediate_event
ev.related_tasks # => #<Set: {}>
ev.add_signal task2.intermediate_event
ev.related_tasks # => #<Set: {task2}>


574
575
576
577
578
579
580
581
582
# File 'lib/roby/event_generator.rb', line 574

def related_tasks(result = nil)
    result ||= Set.new
    for ev in related_events
        if ev.respond_to?(:task)
            result << ev.task
        end
    end
    result
end

#replace_by(object) ⇒ Object



1102
1103
1104
1105
# File 'lib/roby/event_generator.rb', line 1102

def replace_by(object)
    plan.replace_subplan({}, Hash[object => object])
    initialize_replacement(object)
end

#signal(generator, timespec = nil) ⇒ Object



406
407
408
409
# File 'lib/roby/event_generator.rb', line 406

def signal(generator, timespec = nil)
    Roby.warn_deprecated "EventGenerator#signal has been renamed into EventGenerator#signals"
    signals(generator, timespec)
end

#signals(generator, timespec = nil) ⇒ Object

Adds a signal from this event to generator. generator must be controlable.

If time is given it is either a delay: time association, or a at: time association. In the first case, time is a floating-point delay in seconds and in the second case it is a Time object which is the absolute point in time at which this propagation must happen.



395
396
397
398
399
400
401
402
403
404
# File 'lib/roby/event_generator.rb', line 395

def signals(generator, timespec = nil)
    unless generator.controlable?
        raise EventNotControlable.new(self), "trying to establish a signal from #{self} to #{generator} which is not controllable"
    end

    timespec = ExecutionEngine.validate_timespec(timespec)

    add_signal generator, timespec
    self
end

#signals_once(signal, delay = nil) ⇒ Object

Signals the given target event only once



525
526
527
528
529
530
531
# File 'lib/roby/event_generator.rb', line 525

def signals_once(signal, delay = nil)
    signals(signal, delay)
    once do |context|
        remove_signal signal
    end
    self
end

#to_eventObject



556
557
558
# File 'lib/roby/event_generator.rb', line 556

def to_event
    self
end

#to_execution_exceptionObject



1086
1087
1088
# File 'lib/roby/event_generator.rb', line 1086

def to_execution_exception
    LocalizedError.new(self).to_execution_exception
end

#to_execution_exception_matcherObject



1090
1091
1092
# File 'lib/roby/event_generator.rb', line 1090

def to_execution_exception_matcher
    LocalizedError.to_execution_exception_matcher.with_origin(self)
end

#unreachable!(reason = nil, plan = self.plan) ⇒ Object

Called internally when the event becomes unreachable



1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
# File 'lib/roby/event_generator.rb', line 1055

def unreachable!(reason = nil, plan = self.plan)
    engine = execution_engine
    if engine && !engine.in_propagation_context?
        Roby.warn_deprecated "calling EventGenerator#unreachable! outside of a "\
                             "propagation context is deprecated. In tests, "\
                             "use execute { } or expect_execution { }.to { }"
        execution_engine.process_events_synchronous do
            unreachable!(reason, plan)
        end
        return
    end

    if !plan
        raise FinalizedPlanObject,
              "#unreachable! called on #{self} but this is a finalized generator"
    elsif !plan.executable?
        unreachable_without_propagation(reason)
    else
        unreachable_without_propagation(reason, plan)
    end
end

#unreachable_without_propagation(reason = nil, plan = self.plan) ⇒ Object



1029
1030
1031
1032
1033
1034
1035
1036
1037
# File 'lib/roby/event_generator.rb', line 1029

def unreachable_without_propagation(reason = nil, plan = self.plan)
    return if @unreachable

    mark_unreachable!(reason)

    execution_engine.log(:generator_unreachable, self, reason)
    execution_engine&.unreachable_event(self)
    call_unreachable_handlers(reason)
end

#until(limit) ⇒ Object

Returns a new event generator which emits until the limit event is sent

source, target, limit = (1..3).map { EventGenerator.new(true) }
until = target.until(limit).on { |ev| STDERR.puts "FIRED !!!" }
source.signals target

Will do

source.call # => target is emitted
limit.emit
source.call # => target is not emitted anymore

It returns an instance of UntilGenerator with self as parent in the forwarding relation and limit as parent in the signalling relation.

Alternatively, the limitation can be triggered by calling the event's command explicitely:

source.call # => target is emitted
until.call
source.call # => target is not emitted anymore


985
986
987
# File 'lib/roby/event_generator.rb', line 985

def until(limit)
    UntilGenerator.new(self, limit)
end

#when_unreachable(cancel_at_emission = false, &block) ⇒ Object

React to this event being unreachable

If a block is given, that block will be called when the event becomes unreachable. Otherwise, the method returns an EventGenerator instance which will be emitted when it happens.

The cancel_at_emission flag controls if the block (resp. event) should still be called (resp. emitted) after self has been emitted. If true, the handler will be removed if self emits. If false, the handler will be kept.



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/roby/event_generator.rb', line 468

def when_unreachable(cancel_at_emission = false, &block)
    if block_given?
        return if_unreachable(cancel_at_emission: cancel_at_emission, &block)
    end

    # NOTE: the unreachable event is not directly tied to this one from
    # a GC point of view (being able to do this would be useful, but
    # anyway). So, it is possible that it is GCed because the event
    # user did not take care to use it.
    if !@unreachable_events[cancel_at_emission] || !@unreachable_events[cancel_at_emission].plan
        result = EventGenerator.new(true)
        if_unreachable(cancel_at_emission: cancel_at_emission) do
            if result.plan
                result.emit
            end
        end
        add_causal_link result
        @unreachable_events[cancel_at_emission] = result
    end
    @unreachable_events[cancel_at_emission]
end

#|(other) ⇒ Object

Creates a new Event generator which is emitted as soon as one of this object and generator is emitted

See OrGenerator for a complete description.

Note that this operator always creates a new generator, thus

a | b | c | d

will create 3 OrGenerator instances. It is in general better to use | for event pairs, and use OrGenerator#<< when multiple events have to be aggregated:

OrGenerator.new << a << b << c << d



47
48
49
# File 'lib/roby/event_generator.rb', line 47

def |(other)
    OrGenerator.new << self << other
end