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



119
120
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
# File 'lib/roby/event_generator.rb', line 119

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



154
155
156
# File 'lib/roby/event_generator.rb', line 154

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



847
848
849
# File 'lib/roby/event_generator.rb', line 847

def history
  @history
end

#unreachability_reasonObject (readonly)

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



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

def unreachability_reason
  @unreachability_reason
end

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

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

Returns:



412
413
414
# File 'lib/roby/event_generator.rb', line 412

def unreachable_handlers
  @unreachable_handlers
end

Class Method Details

.matchObject



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

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



811
812
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
# File 'lib/roby/event_generator.rb', line 811

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



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

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



981
982
983
984
985
986
987
988
989
990
# File 'lib/roby/event_generator.rb', line 981

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?



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

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



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

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!



1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
# File 'lib/roby/event_generator.rb', line 1007

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



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
250
251
252
253
254
255
256
257
# File 'lib/roby/event_generator.rb', line 223

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



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

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.



895
896
897
898
899
900
901
902
903
904
905
906
907
908
# File 'lib/roby/event_generator.rb', line 895

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:



878
879
880
# File 'lib/roby/event_generator.rb', line 878

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.



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

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



188
189
190
191
192
193
# File 'lib/roby/event_generator.rb', line 188

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.



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

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



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

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

#controlable?Boolean

True if this event is controlable

Returns:

  • (Boolean)


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

def controlable?
    !!@command
end

#create_transaction_proxy(transaction) ⇒ Object



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

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

#default_command(context) ⇒ Object

The default command of emitted the event



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

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

#delay(seconds) ⇒ Object

Returns an event which is emitted seconds seconds after this one



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

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



871
872
873
# File 'lib/roby/event_generator.rb', line 871

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

#emit(*context) ⇒ Object

Emit the event with context as the event context



716
717
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
# File 'lib/roby/event_generator.rb', line 716

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)



645
646
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
# File 'lib/roby/event_generator.rb', line 645

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



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

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



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

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



947
948
949
950
951
# File 'lib/roby/event_generator.rb', line 947

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



993
994
995
996
# File 'lib/roby/event_generator.rb', line 993

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



592
593
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
# File 'lib/roby/event_generator.rb', line 592

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.



918
919
920
921
922
# File 'lib/roby/event_generator.rb', line 918

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

#forward(generator, timespec = nil) ⇒ Object



488
489
490
491
# File 'lib/roby/event_generator.rb', line 488

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



540
541
542
543
# File 'lib/roby/event_generator.rb', line 540

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.



500
501
502
503
504
# File 'lib/roby/event_generator.rb', line 500

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



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

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)


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

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)



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

def garbage!
    super
    unreachable!
end

#happened?Boolean

True if this event has been emitted once.

Returns:

  • (Boolean)


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

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



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

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
# 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
    @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, &block) ⇒ Object



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

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



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

def last
    history.last
end

#mark_unreachable!(reason) ⇒ Object



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

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

#matchObject



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

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

#modelObject

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



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

def model
    self.class
end

#nameObject

The model name



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

def name
    model.name
end

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

Create a new event object for context



583
584
585
586
# File 'lib/roby/event_generator.rb', line 583

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



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

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



535
536
537
538
# File 'lib/roby/event_generator.rb', line 535

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

#pending(sources) ⇒ Object



882
883
884
885
# File 'lib/roby/event_generator.rb', line 882

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

#plan=(plan) ⇒ Object



101
102
103
104
105
106
# File 'lib/roby/event_generator.rb', line 101

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



866
867
868
# File 'lib/roby/event_generator.rb', line 866

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

#pretty_print(pp, context_task: nil) ⇒ Object

:nodoc:



1068
1069
1070
1071
1072
1073
1074
1075
# File 'lib/roby/event_generator.rb', line 1068

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.



790
791
792
# File 'lib/roby/event_generator.rb', line 790

def realize_with(task)
    achieve_with(task)
end

Returns the set of events directly related to this one



559
560
561
# File 'lib/roby/event_generator.rb', line 559

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


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

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



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

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

#signal(generator, timespec = nil) ⇒ Object



404
405
406
407
# File 'lib/roby/event_generator.rb', line 404

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.



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

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



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

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

#to_eventObject



554
555
556
# File 'lib/roby/event_generator.rb', line 554

def to_event
    self
end

#to_execution_exceptionObject



1077
1078
1079
# File 'lib/roby/event_generator.rb', line 1077

def to_execution_exception
    LocalizedError.new(self).to_execution_exception
end

#to_execution_exception_matcherObject



1081
1082
1083
# File 'lib/roby/event_generator.rb', line 1081

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



1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
# File 'lib/roby/event_generator.rb', line 1046

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



1020
1021
1022
1023
1024
1025
1026
1027
1028
# File 'lib/roby/event_generator.rb', line 1020

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


976
977
978
# File 'lib/roby/event_generator.rb', line 976

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.



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

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