Class: Roby::EventGenerator

Inherits:
PlanObject show all
Extended by:
Distributed::DRobyModel::Dump
Includes:
Distributed::EventNotifications, Log::EventGeneratorHooks, Propagation::EventPrecedenceChanged
Defined in:
lib/roby/event.rb,
lib/roby.rb,
lib/roby/distributed/proxy.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:

  • #postponed

  • #calling

  • #called

  • #fired

  • #signalling

  • #forwarding

Defined Under Namespace

Modules: FinalizedEventHook Classes: DRoby

Constant Summary collapse

@@event_gathering =
Hash.new { |h, k| h[k] = ValueSet.new }

Constants included from Log::EventGeneratorHooks

Log::EventGeneratorHooks::HOOKS

Constants included from Log::BasicObjectHooks

Log::BasicObjectHooks::HOOKS

Instance Attribute Summary collapse

Attributes inherited from PlanObject

#executable, #plan, #removed_at

Attributes inherited from BasicObject

#distribute

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Propagation::EventPrecedenceChanged

#added_child_object, #removed_child_object

Methods included from Log::EventGeneratorHooks

#added_child_object, #removed_child_object

Methods inherited from PlanObject

#apply_relation_changes, child_plan_object, #each_plan_child, #finalized?, #forget_peer, #read_write?, #remotely_useful?, #removing_child_object, #replace_by, #replace_subplan_by, #root_object, #root_object?, #subscribed?, #update_on?, #updated_by?

Methods included from Distributed::RelationModificationHooks

#added_child_object, #removed_child_object

Methods included from Transactions::PlanObjectUpdates

#adding_child_object, #removing_child_object

Methods included from DirectedRelationSupport

#add_parent_object, #check_is_relation, #related_objects, #relations, #remove_child_object, #remove_children, #remove_parent_object, #remove_parents, #remove_relations

Methods inherited from BasicObject

#add_sibling_for, #distribute?, distribute?, #finalized?, #forget_peer, #has_sibling_on?, local_only, #read_write?, #remotely_useful?, #remove_sibling_for, #self_owned?, #sibling_of, #sibling_on, #subscribe, #subscribed?, #update_on?, #updated?, #updated_by?, #updated_peers

Methods included from Log::BasicObjectHooks

#added_owner, #removed_owner

Constructor Details

#initialize(command_object = nil, &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



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

def initialize(command_object = nil, &command_block)
    @preconditions = []
    @handlers = []
    @pending  = false
    @unreachable = false
    @unreachable_handlers = []

    if command_object || command_block
	self.command = if command_object.respond_to?(:call)
			   command_object
		       elsif command_block
			   command_block
		       else
			   method(:default_command)
		       end
    end
    super() if defined? super
    @executable = true
end

Instance Attribute Details

#commandObject

The current command block



181
182
183
# File 'lib/roby/event.rb', line 181

def command
  @command
end

#executable=(value) ⇒ Object (writeonly)

Sets the attribute executable

Parameters:

  • value

    the value to set the attribute executable to.



111
112
113
# File 'lib/roby/event.rb', line 111

def executable=(value)
  @executable = value
end

#pendingObject (readonly)

The count of command calls that have not a corresponding emission



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

def pending
  @pending
end

#unreachable_handlersObject (readonly)

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



293
294
295
# File 'lib/roby/event.rb', line 293

def unreachable_handlers
  @unreachable_handlers
end

Class Method Details

._load(str) ⇒ Object

:nodoc:



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

def self._load(str) # :nodoc:
    Marshal.load(str) 
end

.event_gatheringObject

An array of [collection, events] elements, collection being the object in which we must add the fired events, and events the set of event generators collection is listening for.



707
# File 'lib/roby/event.rb', line 707

def self.event_gathering; @@event_gathering end

.gather_events(collection, events) ⇒ Object

If a generator in events fires, add the fired event in collection



692
693
694
695
696
# File 'lib/roby/event.rb', line 692

def self.gather_events(collection, events)
    for ev in events
	event_gathering[ev] << collection
    end
end

.remove_event_gathering(collection) ⇒ Object

Remove the notifications that have been registered for collection



698
699
700
701
702
703
# File 'lib/roby/event.rb', line 698

def self.remove_event_gathering(collection)
    @@event_gathering.delete_if do |_, collections| 
	collections.delete(collection)
	collections.empty?
    end
end

Instance Method Details

#&(generator) ⇒ Object

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



125
126
127
# File 'lib/roby/event.rb', line 125

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

#_dump(lvl) ⇒ Object

:nodoc:



94
95
96
# File 'lib/roby/distributed/proxy.rb', line 94

def _dump(lvl) # :nodoc:
    Marshal.dump(remote_id) 
end

#achieve_with(ev) ⇒ Object

If ev becomes unreachable, an EmissionFailed exception will be raised. If a block is given, it is supposed to return the context of the event emitted by self, given the context of the event emitted by ev.

From an event propagation point of view, it looks like: TODO: add a figure



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/roby/event.rb', line 538

def achieve_with(ev)
    stack = caller(1)
    if block_given?
	ev.add_causal_link self
	ev.once do |context|
	    self.emit(yield(context))
	end
    else
	ev.forward_once self
    end

    ev.if_unreachable(true) do |reason|
	msg = "#{ev} is unreachable#{ " (#{reason})" if reason }, in #{stack.first}"
	if ev.respond_to?(:task)
	    msg << "\n  " << ev.task.history.map { |ev| "#{ev.time.to_hms} #{ev.symbol}: #{ev.context}" }.join("\n  ")
	end
	emit_failed(UnreachableEvent.new(self, reason), msg)
    end
end

#add_child_object(child, type, info) ⇒ Object

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



682
683
684
685
686
687
688
# File 'lib/roby/event.rb', line 682

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?



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
# File 'lib/roby/event.rb', line 224

def call(*context)
    if !self_owned?
	raise OwnershipError, "not owner"
    elsif !controlable?
	raise EventNotControlable.new(self), "#call called on a non-controlable event"
    elsif !executable?
	raise EventNotExecutable.new(self), "#call called on #{self} which is non-executable event"
    elsif !Roby.inside_control?
	raise ThreadMismatch, "#call called while not in control thread"
    end

    context.compact!
    if Propagation.gathering?
	Propagation.add_event_propagation(false, Propagation.sources, self, (context unless context.empty?), nil)
    else
	Roby::Control.synchronize do
	    errors = Propagation.propagate_events do |initial_set|
		Propagation.add_event_propagation(false, nil, self, (context unless context.empty?), nil)
	    end
	    if errors.size == 1
		e = errors.first.exception
		raise e, e.message, e.backtrace
	    elsif !errors.empty?
		for e in errors
		    STDERR.puts e.exception.full_message
		end
	    end
	end
    end
end

#call_handlers(event) ⇒ Object

Call the event handlers defined for this event generator



421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/roby/event.rb', line 421

def call_handlers(event)
    # Since we are in a gathering context, call
    # to other objects are not done, but gathered in the 
    # :propagation TLS
    each_handler do |h| 
	begin
	    h.call(event)
	rescue Exception => e
	    Propagation.add_error( EventHandlerError.new(e, event) )
	end
    end
end

#call_without_propagation(context) ⇒ Object

Returns true if the command has been called and false otherwise The command won’t be called if #postpone() is called within the #calling hook

This is used by propagation code, and should never be called directly



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/roby/event.rb', line 191

def call_without_propagation(context) # :nodoc:
    if !controlable?
	raise EventNotControlable.new(self), "#call called on a non-controlable event"
    end

    postponed = catch :postponed do 
	calling(context)
	@pending = true

	Propagation.propagation_context([self]) do
	    command[context]
	end

	false
    end

    if postponed
	@pending = false
	postponed(context, *postponed)
	false
    else
	called(context)
	true
    end

rescue Exception
    @pending = false
    raise
end

#called(context) ⇒ Object

Hook called just after the event command has been called



622
# File 'lib/roby/event.rb', line 622

def called(context); super if defined? super 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.

The #postpone method can be called in this hook



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
# File 'lib/roby/event.rb', line 605

def calling(context)
    super if defined? super 
    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:



596
597
598
# File 'lib/roby/event.rb', line 596

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

#controlable?Boolean

True if this event is controlable

Returns:

  • (Boolean)


184
# File 'lib/roby/event.rb', line 184

def controlable?; !!@command end

#default_command(context) ⇒ Object



176
177
178
# File 'lib/roby/event.rb', line 176

def default_command(context)
    emit(*context)
end

#delay(seconds) ⇒ Object

Returns an event which is emitted seconds seconds after this one



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

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

#droby_dump(dest) ⇒ Object

Returns an intermediate representation of self suitable to be sent to the dest peer.



103
104
105
106
107
# File 'lib/roby/distributed/proxy.rb', line 103

def droby_dump(dest)
    DRoby.new(remote_siblings.droby_dump(dest), owners.droby_dump(dest),
	      model.droby_dump(dest),  plan.droby_dump(dest), 
	      controlable?, happened?)
end

#each_preconditionObject

Yields all precondition handlers defined for this generator



576
577
578
# File 'lib/roby/event.rb', line 576

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

#emit(*context) ⇒ Object

Emit the event with context as the event context



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/roby/event.rb', line 484

def emit(*context)
    if !executable?
	raise EventNotExecutable.new(self), "#emit called on #{self} which is not executable"
    elsif !self_owned?
	raise OwnershipError, "cannot emit an event we don't own. #{self} is owned by #{owners}"
    elsif !Roby.inside_control?
	raise ThreadMismatch, "#emit called while not in control thread"
    end

    context.compact!
    if Propagation.gathering?
	Propagation.add_event_propagation(true, Propagation.sources, self, (context unless context.empty?), nil)
    else
	Roby::Control.synchronize do
	    errors = Propagation.propagate_events do |initial_set|
		Propagation.add_event_propagation(true, Propagation.sources, self, (context unless context.empty?), nil)
	    end
	    if errors.size == 1
		e = errors.first.exception
		raise e, e.message, e.backtrace
	    elsif !errors.empty?
		for e in errors
		    STDERR.puts e.full_message
		end
	    end
	end
    end
end

#emit_failed(*what) ⇒ Object

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



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/roby/event.rb', line 436

def emit_failed(*what)
    what, message = *what
    what ||= EmissionFailed

    if !message && what.respond_to?(:to_str)
	message = what.to_str
	what = EmissionFailed
    end

    failure_message = "failed to emit #{self}: #{message}"
    error = if Class === what then what.new(nil, self)
	    else what
	    end
    error = error.exception failure_message

    Propagation.add_error(error)

ensure
    @pending = false
end

#emit_on(generator, timespec = nil) ⇒ Object

Deprecated. Instead of using

dest.emit_on(source)

now use

source.forward(dest)


517
518
519
520
# File 'lib/roby/event.rb', line 517

def emit_on(generator, timespec = nil)
    generator.forward(self, timespec)
    self
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



462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/roby/event.rb', line 462

def emit_without_propagation(context) # :nodoc:
    if !executable?
	raise EventNotExecutable.new(self), "#emit called on #{self} which is not executable"
    end

    emitting(context)

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

    true

ensure
    @pending = false
end

#emitting(context) ⇒ Object

Hook called when this event will be emitted



649
# File 'lib/roby/event.rb', line 649

def emitting(context); super if defined? super end

#executable?Boolean

True if this event is executable. A non-executable event cannot be called even if it is controlable

Returns:

  • (Boolean)


115
# File 'lib/roby/event.rb', line 115

def executable?; @executable 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



659
660
661
662
663
# File 'lib/roby/event.rb', line 659

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

#fired(event) ⇒ Object

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



626
627
628
629
630
631
632
633
634
635
636
637
# File 'lib/roby/event.rb', line 626

def fired(event)
    unreachable_handlers.delete_if { |cancel, _| cancel }

    history << event
    if EventGenerator.event_gathering.has_key?(event.generator)
	for c in EventGenerator.event_gathering[event.generator]
	    c << event
	end
    end

    super if defined? super
end

#forward(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.



328
329
330
331
332
# File 'lib/roby/event.rb', line 328

def forward(generator, timespec = nil)
    timespec = Propagation.validate_timespec(timespec)
    add_forwarding generator, timespec
    self
end

#forward_once(ev) ⇒ Object

Forwards to ev only once



361
362
363
364
365
366
# File 'lib/roby/event.rb', line 361

def forward_once(ev)
    forward(ev)
    once do
	remove_forwarding ev
    end
end

#forwarding(event, to) ⇒ Object

Hook called just before the propagation forwards self to to. event is the Event object which has been generated by this model



646
# File 'lib/roby/event.rb', line 646

def forwarding(event, to); super if defined? super end

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

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



296
297
298
299
# File 'lib/roby/event.rb', line 296

def if_unreachable(cancel_at_emission = false, &block)
    unreachable_handlers << [cancel_at_emission, block]
    block.object_id
end

#initialize_copy(old) ⇒ Object

:nodoc:



131
132
133
134
135
# File 'lib/roby/event.rb', line 131

def initialize_copy(old) # :nodoc:
    super

    @history = old.history.dup
end

#lastObject

Last event to have been emitted by this generator



565
# File 'lib/roby/event.rb', line 565

def last; history.last end

#modelObject



137
# File 'lib/roby/event.rb', line 137

def model; self.class end

#nameObject

The model name



139
# File 'lib/roby/event.rb', line 139

def name; model.name end

#new(context) ⇒ Object

Create a new event object for context



384
# File 'lib/roby/event.rb', line 384

def new(context); Event.new(self, Propagation.propagation_id, context, Time.now) end

#on(signal = nil, time = nil, &handler) ⇒ Object

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.



262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/roby/event.rb', line 262

def on(signal = nil, time = nil, &handler)
    if signal
	self.signal(signal, time)
    end

    if handler
	check_arity(handler, 1)
	self.handlers << handler
    end

    self
end

#once(signal = nil, time = nil) ⇒ Object

Equivalent to #on, but call the handler and/or signal the target event only once.



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

def once(signal = nil, time = nil)
    handler = nil
    on(signal, time) do |context|
	yield(context) if block_given?
	self.handlers.delete(handler)
	remove_signal(signal) if signal
    end
    handler = self.handlers.last
end

#pending?Boolean

True if this event has been called but is not emitted yet

Returns:

  • (Boolean)


143
# File 'lib/roby/event.rb', line 143

def pending?; pending end

#postpone(generator, reason = nil) ⇒ Object

Call #postpone in #calling to announce that the event should not be called now, but should be called back when generator is emitted

A reason string can be provided for debugging purposes



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

def postpone(generator, reason = nil)
    generator.on self
    yield if block_given?
    throw :postponed, [generator, reason]
end

#postponed(context, generator, reason) ⇒ Object

Hook called when the event has been postponed. See #postpone



591
# File 'lib/roby/event.rb', line 591

def postponed(context, generator, reason); super if defined? super 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



571
572
573
# File 'lib/roby/event.rb', line 571

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

#pretty_print(pp) ⇒ Object

:nodoc:



738
739
740
741
742
743
744
745
746
747
748
749
# File 'lib/roby/event.rb', line 738

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 }

	pp.breakable
	pp.text "relations: "
	pp.seplist(relations) { |r| pp.text r.name }
    end
end

#realize_with(task) ⇒ Object

For backwards compatibility. Use #achieve_with.



558
# File 'lib/roby/event.rb', line 558

def realize_with(task); achieve_with(task) end

Returns the set of events directly related to this one



371
# File 'lib/roby/event.rb', line 371

def related_events(result = nil); related_objects(nil, result) end

Returns the set of tasks directly related to this event



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

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

#signal(generator, timespec = nil) ⇒ Object

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.



282
283
284
285
286
287
288
289
290
# File 'lib/roby/event.rb', line 282

def signal(generator, timespec = nil)
    if !generator.controlable?
	raise EventNotControlable.new(self), "trying to establish a signal between #{self} and #{generator}"
    end
    timespec = Propagation.validate_timespec(timespec)

    add_signal generator, timespec
    self
end

#signal_once(signal, time = nil) ⇒ Object

Signal the signal event the first time this event is emitted. If time is non-nil, delay the signalling this many seconds.



346
# File 'lib/roby/event.rb', line 346

def signal_once(signal, time = nil); once(signal, time) end

#signalling(event, to) ⇒ Object

Hook called just before the to generator is signalled by this generator. event is the Event object which has been generated by this model



642
# File 'lib/roby/event.rb', line 642

def signalling(event, to); super if defined? super end

#to_eventObject



368
# File 'lib/roby/event.rb', line 368

def to_event; self end

#unreachable!(reason = nil) ⇒ Object

Called internally when the event becomes unreachable



722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
# File 'lib/roby/event.rb', line 722

def unreachable!(reason = nil)
    return if @unreachable
    @unreachable = true

           EventGenerator.event_gathering.delete(self)

    unreachable_handlers.each do |_, block|
	begin
	    block.call(reason)
	rescue Exception => e
	    Propagation.add_error(EventHandlerError.new(e, self))
	end
    end
    unreachable_handlers.clear
end

#until(limit) ⇒ Object

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

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

Will do

source.call # => FIRED !!!
limit.emit
source.call # =>

See also UntilGenerator



679
# File 'lib/roby/event.rb', line 679

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

#when_unreachableObject

Returns an event which will be emitted when this event becones unreachable



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/roby/event.rb', line 303

def when_unreachable
    # 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_event || !@unreachable_event.plan
        result = EventGenerator.new(true)
        if_unreachable(false) do
            if result.plan
                result.emit
            end
        end
        add_causal_link result
        @unreachable_event = result
    end
    @unreachable_event
end

#|(generator) ⇒ Object

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



119
120
121
# File 'lib/roby/event.rb', line 119

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