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

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



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/roby/event_generator.rb', line 112

def initialize(command_object = nil, controlable: false, plan: TemplatePlan.new, &command_block)
    @preconditions   = []
    @handlers        = []
    @pending         = false
    @pending_sources = []
    @unreachable     = false
    @unreachable_events   = Hash.new
    @unreachable_handlers = []
    @history       = Array.new
    @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.



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

def all_relation_spaces
  @all_relation_spaces
end

.relation_spacesObject (readonly)

Returns the value of attribute relation_spaces.



20
21
22
# File 'lib/roby/event_generator.rb', line 20

def relation_spaces
  @relation_spaces
end

Instance Attribute Details

#commandObject

The current command block



146
147
148
# File 'lib/roby/event_generator.rb', line 146

def command
  @command
end

#event_modelObject (readonly)

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

Defaults to Event



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

def event_model
  @event_model
end

#historyObject (readonly)

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



794
795
796
# File 'lib/roby/event_generator.rb', line 794

def history
  @history
end

#unreachability_reasonObject (readonly)

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



944
945
946
# File 'lib/roby/event_generator.rb', line 944

def unreachability_reason
  @unreachability_reason
end

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

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

Returns:



391
392
393
# File 'lib/roby/event_generator.rb', line 391

def unreachable_handlers
  @unreachable_handlers
end

Class Method Details

.matchObject



1024
1025
1026
# File 'lib/roby/event_generator.rb', line 1024

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

Instance Method Details

#&(generator) ⇒ 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


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

def &(generator)
    AndGenerator.new << self << generator
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



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

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 ![: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|
            if on_failure == :fail
                emit_failed(reason)
            elsif on_failure == :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



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

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



924
925
926
927
928
929
930
# File 'lib/roby/event_generator.rb', line 924

def add_child_object(child, type, info) # :nodoc:
    if !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?



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/roby/event_generator.rb', line 259

def call(*context)
    engine = execution_engine
    if engine && !engine.in_propagation_context?
        Roby.warn_deprecated "calling EventGenerator#call outside of 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
    if !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



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/roby/event_generator.rb', line 586

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) )
        end
        h.once?
    end
    handlers.delete_if { |h| processed_once_handlers.include?(h) }
end

#call_unreachable_handlers(reason) ⇒ Object

Internal helper for unreachable!



947
948
949
950
951
952
953
954
955
956
957
958
# File 'lib/roby/event_generator.rb', line 947

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



213
214
215
216
217
218
219
220
221
222
223
224
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
# File 'lib/roby/event_generator.rb', line 213

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
        if !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



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

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.



838
839
840
841
842
843
844
845
846
847
848
849
850
851
# File 'lib/roby/event_generator.rb', line 838

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

        if !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:



821
822
823
# File 'lib/roby/event_generator.rb', line 821

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.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/roby/event_generator.rb', line 153

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



178
179
180
181
182
183
# File 'lib/roby/event_generator.rb', line 178

def check_call_validity_after_calling
    if !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.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/roby/event_generator.rb', line 187

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



830
831
832
833
# File 'lib/roby/event_generator.rb', line 830

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

#controlable?Boolean

True if this event is controlable

Returns:

  • (Boolean)


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

def controlable?; !!@command end

#create_transaction_proxy(transaction) ⇒ Object



1037
1038
1039
# File 'lib/roby/event_generator.rb', line 1037

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

#default_command(context) ⇒ Object

The default command of emitted the event



141
142
143
# File 'lib/roby/event_generator.rb', line 141

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

#delay(seconds) ⇒ Object

Returns an event which is emitted seconds seconds after this one



481
482
483
484
485
486
487
488
# File 'lib/roby/event_generator.rb', line 481

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

#each_preconditionObject

Yields all precondition handlers defined for this generator



814
815
816
# File 'lib/roby/event_generator.rb', line 814

def each_precondition # :yield:reason, block
    @preconditions.each { |o| yield(o) } 
end

#emit(*context) ⇒ Object

Emit the event with context as the event context



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
# File 'lib/roby/event_generator.rb', line 673

def emit(*context)
    engine = execution_engine
    if engine && !engine.in_propagation_context?
        Roby.warn_deprecated "calling EventGenerator#emit outside of 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
    if !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)



606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
# File 'lib/roby/event_generator.rb', line 606

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 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

    if !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



651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
# File 'lib/roby/event_generator.rb', line 651

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)
    if !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



858
859
# File 'lib/roby/event_generator.rb', line 858

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



892
893
894
895
896
# File 'lib/roby/event_generator.rb', line 892

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



933
934
935
936
# File 'lib/roby/event_generator.rb', line 933

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



555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/roby/event_generator.rb', line 555

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.



863
864
865
866
867
# File 'lib/roby/event_generator.rb', line 863

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

#forward(generator, timespec = nil) ⇒ Object



462
463
464
465
# File 'lib/roby/event_generator.rb', line 462

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



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

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.



474
475
476
477
478
# File 'lib/roby/event_generator.rb', line 474

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



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

def forward_to_once(ev, delay = nil)
    forward_to(ev, delay)
    once do |context|
        remove_forwarding ev
    end
    self
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)



981
982
983
984
# File 'lib/roby/event_generator.rb', line 981

def garbage!
    super
    unreachable!
end

#happened?Boolean

True if this event has been emitted once.

Returns:

  • (Boolean)


798
799
800
801
# File 'lib/roby/event_generator.rb', line 798

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

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

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

Parameters:

  • options (Hash) (defaults to: Hash.new)

    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



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/roby/event_generator.rb', line 408

def if_unreachable(options = Hash.new, &block)
    if options == true || options == false
        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

    if ![: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:



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

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
    @pending  = false
    if old.command.kind_of?(Method)
        @command = method(old.command.name)
    end
    @unreachable = old.unreachable?
    @unreachable_handlers = old.unreachable_handlers.dup
end

#initialize_replacement(event) ⇒ Object



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/roby/event_generator.rb', line 344

def initialize_replacement(event)
    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, &Proc.new)
    end
end

#lastObject

Last event to have been emitted by this generator



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

def last; history.last end

#mark_unreachable!(reason) ⇒ Object



971
972
973
974
975
# File 'lib/roby/event_generator.rb', line 971

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

#matchObject



1028
1029
1030
# File 'lib/roby/event_generator.rb', line 1028

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

#modelObject

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



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

def model; self.class end

#nameObject

The model name



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

def name; model.name end

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

Create a new event object for context



546
547
548
549
# File 'lib/roby/event_generator.rb', line 546

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



335
336
337
338
339
340
341
342
# File 'lib/roby/event_generator.rb', line 335

def on(on_replace: :drop, once: false, &handler)
    if ![: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 = Hash.new, &block) ⇒ Object

call-seq:

once { |context| ... }

Calls the provided event handler only once



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

def once(options = Hash.new, &block)
    on(options.merge(once: true), &block)
    self
end

#pending(sources) ⇒ Object



825
826
827
828
# File 'lib/roby/event_generator.rb', line 825

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

#plan=(plan) ⇒ Object



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

def plan=(plan)
    super
    @relation_graphs = if plan then plan.event_relation_graphs
                       end
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



809
810
811
# File 'lib/roby/event_generator.rb', line 809

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

#pretty_print(pp) ⇒ Object

:nodoc:



1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/roby/event_generator.rb', line 1006

def pretty_print(pp) # :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.



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

def realize_with(task); achieve_with(task) end

Returns the set of events directly related to this one



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

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}>


535
536
537
538
539
540
541
542
543
# File 'lib/roby/event_generator.rb', line 535

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



1032
1033
1034
1035
# File 'lib/roby/event_generator.rb', line 1032

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

#signal(generator, timespec = nil) ⇒ Object



383
384
385
386
# File 'lib/roby/event_generator.rb', line 383

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.



373
374
375
376
377
378
379
380
381
# File 'lib/roby/event_generator.rb', line 373

def signals(generator, timespec = nil)
    if !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



491
492
493
494
495
496
497
# File 'lib/roby/event_generator.rb', line 491

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

#to_eventObject



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

def to_event; self end

#to_execution_exceptionObject



1015
1016
1017
# File 'lib/roby/event_generator.rb', line 1015

def to_execution_exception
    LocalizedError.new(self).to_execution_exception
end

#to_execution_exception_matcherObject



1019
1020
1021
# File 'lib/roby/event_generator.rb', line 1019

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



987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
# File 'lib/roby/event_generator.rb', line 987

def unreachable!(reason = nil, plan = self.plan)
    engine = execution_engine
    if engine && !engine.in_propagation_context?
        Roby.warn_deprecated "calling EventGenerator#unreachable! outside of 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



960
961
962
963
964
965
966
967
968
969
# File 'lib/roby/event_generator.rb', line 960

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

    execution_engine.log(:generator_unreachable, self, reason)
    if execution_engine
        execution_engine.unreachable_event(self)
    end
    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


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

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.



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/roby/event_generator.rb', line 440

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

#|(generator) ⇒ 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


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

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