Class: Roby::Plan

Inherits:
DistributedObject show all
Extended by:
Logger::Forward, Logger::Hierarchy
Includes:
DRoby::Identifiable, DRoby::V5::PlanDumper, EventLogging::Mixin, GUI::GraphvizPlan, GUI::RelationsCanvasPlan
Defined in:
lib/roby/plan.rb,
lib/roby.rb,
lib/roby/event_structure/temporal_constraints.rb,
lib/roby/droby/enable.rb

Overview

A plan object manages a collection of tasks and events.

Defined Under Namespace

Classes: ReplacementFilter, Trigger, UsefulFreeEventVisitor

Constant Summary

Constants included from GUI::RelationsCanvasPlan

GUI::RelationsCanvasPlan::PLAN_STROKE_WIDTH

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes included from GUI::RelationsCanvasPlan

#depth, #max_depth

Attributes included from GUI::GraphvizPlan

#depth, #layout_level

Attributes inherited from DistributedObject

#local_owner_id, #owners

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DRoby::V5::PlanDumper

#droby_dump

Methods included from DRoby::Identifiable

#droby_id, #initialize_copy

Methods included from GUI::RelationsCanvasPlan

#display, #display_create, #display_name, #display_parent

Methods included from GUI::GraphvizPlan

#all_events, #apply_layout, #compute_depth, #each_displayed_relation, #each_edge, #each_layout_relation, #layout_relations, #relations_to_dot, #to_dot

Methods included from EventLogging::Mixin

#log, #log_flush_cycle, #log_queue_size, #log_timepoint, #log_timepoint_group, #log_timepoint_group_end, #log_timepoint_group_start

Methods inherited from DistributedObject

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

Constructor Details

#initialize(graph_observer: nil) ⇒ Plan

Returns a new instance of Plan.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/roby/plan.rb', line 88

def initialize(graph_observer: nil)
    @local_owner = DRoby::PeerID.new("local")

    @tasks = Set.new
    @free_events = Set.new
    @task_events = Set.new
    @transactions = Set.new
    @fault_response_tables = []
    @triggers = []

    @plan_services = {}

    @event_logger = EventLogging::AggregateEventLogger.new
    @active_fault_response_tables = []
    @task_index = Roby::Queries::Index.new

    @graph_observer = graph_observer
    create_relations
    create_null_relations

    super()
end

Class Attribute Details

.structure_checks {|the| ... } ⇒ Object (readonly)

A set of structure checking procedures that must be performed on all plans

Yield Parameters:

  • the (Plan)

    plan

Yield Returns:

  • (Array<(#to_execution_exception,Array<Task>)>)

    a list of exceptions, and the tasks toward which these exceptions should be propagated. If the list of tasks is nil, all parents of the exception's origin will be selected



1786
1787
1788
# File 'lib/roby/plan.rb', line 1786

def structure_checks
  @structure_checks
end

Instance Attribute Details

#active_fault_response_tablesObject (readonly)

The list of fault response tables that are currently globally active on this plan



1950
1951
1952
# File 'lib/roby/plan.rb', line 1950

def active_fault_response_tables
  @active_fault_response_tables
end

#event_loggerObject (readonly)

The event logger



83
84
85
# File 'lib/roby/plan.rb', line 83

def event_logger
  @event_logger
end

#event_relation_graphsObject (readonly)

The graphs that make event relations, formatted as required by Relations::DirectedRelationSupport#relation_graphs



186
187
188
# File 'lib/roby/plan.rb', line 186

def event_relation_graphs
  @event_relation_graphs
end

#free_eventsObject (readonly)

The list of events that are not included in a task



36
37
38
# File 'lib/roby/plan.rb', line 36

def free_events
  @free_events
end

#graph_observerObject (readonly)

The observer object that reacts to relation changes



86
87
88
# File 'lib/roby/plan.rb', line 86

def graph_observer
  @graph_observer
end

#local_ownerObject

The Peer ID of the local owner (i.e. of the local process / execution engine)



12
13
14
# File 'lib/roby/plan.rb', line 12

def local_owner
  @local_owner
end

#null_event_relation_graphsObject (readonly)

A set of empty graphs that match #event_relation_graphs

Used for finalized events



191
192
193
# File 'lib/roby/plan.rb', line 191

def null_event_relation_graphs
  @null_event_relation_graphs
end

#null_task_relation_graphsObject (readonly)

A set of empty graphs that match #task_relation_graphs

Used for finalized tasks



180
181
182
# File 'lib/roby/plan.rb', line 180

def null_task_relation_graphs
  @null_task_relation_graphs
end

#plan_servicesObject (readonly)

The set of PlanService instances that are defined on this plan



63
64
65
# File 'lib/roby/plan.rb', line 63

def plan_services
  @plan_services
end

#structure_checks {|the| ... } ⇒ Object (readonly)

The set of blocks that should be called to check the structure of the plan.

Yield Parameters:

  • the (Plan)

    plan

Yield Returns:

  • (Array<(#to_execution_exception,Array<Task>)>)

    a list of exceptions, and the tasks toward which these exceptions should be propagated. If the list of tasks is nil, all parents of the exception's origin will be selected



1775
1776
1777
# File 'lib/roby/plan.rb', line 1775

def structure_checks
  @structure_checks
end

#task_eventsObject (readonly)

The set of events that are defined by #tasks



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

def task_events
  @task_events
end

#task_indexObject (readonly)

The task index for this plan. This is a Queries::Index object which allows efficient resolving of queries.



16
17
18
# File 'lib/roby/plan.rb', line 16

def task_index
  @task_index
end

#task_relation_graphsObject (readonly)

The graphs that make task relations, formatted as required by Relations::DirectedRelationSupport#relation_graphs



175
176
177
# File 'lib/roby/plan.rb', line 175

def task_relation_graphs
  @task_relation_graphs
end

#tasksObject (readonly)

The list of tasks that are included in this plan



19
20
21
# File 'lib/roby/plan.rb', line 19

def tasks
  @tasks
end

#transactionsObject (readonly)

The set of transactions which are built on top of this plan



51
52
53
# File 'lib/roby/plan.rb', line 51

def transactions
  @transactions
end

#triggersObject (readonly)

A set of pair of task matching objects and blocks defining this plan's triggers

See #add_trigger



48
49
50
# File 'lib/roby/plan.rb', line 48

def triggers
  @triggers
end

Class Method Details

.can_gc?(task) ⇒ Boolean

Returns:

  • (Boolean)


1542
1543
1544
1545
1546
1547
1548
1549
1550
# File 'lib/roby/plan.rb', line 1542

def self.can_gc?(task)
    if task.starting?
        true # wait for the task to be started before deciding ...
    elsif task.running? && !task.finishing?
        task.event(:stop).controlable?
    else
        true
    end
end

.check_failed_missions(plan) ⇒ Object

Get all missions that have failed



1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
# File 'lib/roby/plan.rb', line 1790

def self.check_failed_missions(plan)
    result = []
    plan.mission_tasks.each do |task|
        result << MissionFailedError.new(task) if task.failed?
    end
    plan.permanent_tasks.each do |task|
        result << PermanentTaskError.new(task) if task.failed?
    end
    result
end

.instanciate_relation_graphs(graph_observer: nil) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/roby/plan.rb', line 141

def self.instanciate_relation_graphs(graph_observer: nil)
    task_relation_graphs = Relations::Space.new_relation_graph_mapping
    Task.all_relation_spaces.each do |space|
        task_relation_graphs.merge!(
            space.instanciate(observer: graph_observer)
        )
    end

    event_relation_graphs = Relations::Space.new_relation_graph_mapping
    EventGenerator.all_relation_spaces.each do |space|
        event_relation_graphs.merge!(
            space.instanciate(observer: graph_observer)
        )
    end
    [task_relation_graphs, event_relation_graphs]
end

Instance Method Details

#[](object, create = true) ⇒ Object

Returns object if object is a plan object from this plan, or if it has no plan yet (in which case it is added to the plan first). Otherwise, raises ArgumentError.

This method is provided for consistency with Transaction#[]



1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
# File 'lib/roby/plan.rb', line 1528

def [](object, create = true)
    if object.plan == self
        object
    elsif !object.finalized? && object.plan.template?
        add(object)
        object
    elsif object.finalized? && create
        raise ArgumentError,
              "#{object} is has been finalized, and can't be reused"
    else
        raise ArgumentError, "#{object} is not from #{self}"
    end
end

#add(objects) ⇒ Object

call-seq:

plan.add(task) => plan
plan.add(event) => plan
plan.add([task, event, task2, ...]) => plan
plan.add([t1, t2, ...]) => plan

Adds the subplan of the given tasks and events into the plan.

That means that it adds the listed tasks/events and the task/events that are reachable through any relations).



1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
# File 'lib/roby/plan.rb', line 1111

def add(objects)
    is_scalar = objects.respond_to?(:each)
    objects = normalize_add_arguments(objects)

    plans = Set.new
    objects.each do |plan_object|
        p = plan_object.plan
        next if p == self

        if plan_object.removed_at
            raise ArgumentError,
                  "cannot add #{plan_object} in #{self}, "\
                  "it has been removed from the plan"
        elsif !p
            raise InternalError,
                  "there seem to be an inconsistency, #{plan_object}#plan "\
                  "is nil but #removed_at is not set"
        elsif p.empty?
            raise InternalError,
                  "there seem to be an inconsistency, #{plan_object} "\
                  "is associated with #{p} but #{p} is empty"
        elsif !p.template?
            raise ModelViolation,
                  "cannot add #{plan_object} in #{self}, "\
                  "it is already included in #{p}"
        end
        plans << p
    end

    plans.each do |p|
        merge!(p)
    end

    if is_scalar
        objects.first
    else
        objects
    end
end

#add_event_logger(logger) ⇒ Object



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

def add_event_logger(logger)
    @event_logger.add(logger)
end

#add_job_action(action) ⇒ Object

Add an action as a job



555
556
557
558
559
# File 'lib/roby/plan.rb', line 555

def add_job_action(action)
    add_mission_task(
        action.as_plan(job_id: Roby::Interface::Job.allocate_job_id)
    )
end

#add_mission(task) ⇒ Object

Deprecated.

use #add_mission_task instead



510
511
512
513
514
515
# File 'lib/roby/plan.rb', line 510

def add_mission(task)
    Roby.warn_deprecated(
        "#add_mission is deprecated, use #add_mission_task instead"
    )
    add_mission_task(task)
end

#add_mission_task(task) ⇒ Object

Add a task to the plan's set of missions

A mission represents the system's overall goal. As such a mission task and all its dependencies are protected against the garbage collection mechanisms, and the emission of a mission's failed event causes a MissionFailedError exception to be generated.

Note that this method should be used to add the task to the plan and mark it as mission, and to mark an already added task as mission as well.



543
544
545
546
547
548
549
550
551
552
# File 'lib/roby/plan.rb', line 543

def add_mission_task(task)
    task = normalize_add_arguments([task]).first
    return if mission_tasks.include?(task)

    add([task])
    mission_tasks << task
    task.mission = true if task.self_owned?
    notify_task_status_change(task, :mission)
    task
end

#add_permanent(object) ⇒ Object

Deprecated.


587
588
589
590
591
592
593
594
595
596
597
598
599
# File 'lib/roby/plan.rb', line 587

def add_permanent(object)
    Roby.warn_deprecated(
        "#add_permanent is deprecated, use either #add_permanent_task "\
        "or #add_permanent_event instead"
    )
    object = normalize_add_arguments([object]).first
    if object.respond_to?(:to_task)
        add_permanent_task(object)
    else
        add_permanent_event(object)
    end
    object
end

#add_permanent_event(event) ⇒ Object

Mark an event as permanent, optionally adding to the plan

Permanent events are protected against garbage collection



672
673
674
675
676
677
678
679
680
# File 'lib/roby/plan.rb', line 672

def add_permanent_event(event)
    event = normalize_add_arguments([event]).first
    return if permanent_events.include?(event)

    add([event])
    permanent_events << event
    notify_event_status_change(event, :permanent)
    event
end

#add_permanent_task(task) ⇒ Object

Mark a task as permanent, optionally adding to the plan

Permanent tasks are protected against garbage collection. Like missions, failure of a permanent task will generate a plan exception Roby::PermanentTaskError. Unlike missions, this exception is non-fatal.



638
639
640
641
642
643
644
645
646
# File 'lib/roby/plan.rb', line 638

def add_permanent_task(task)
    task = normalize_add_arguments([task]).first
    return if permanent_tasks.include?(task)

    add([task])
    permanent_tasks << task
    notify_task_status_change(task, :permanent)
    task
end

#add_plan_service(service) ⇒ Object

Register a new plan service on this plan



930
931
932
933
934
935
936
937
938
939
940
# File 'lib/roby/plan.rb', line 930

def add_plan_service(service)
    if service.task.plan != self
        raise ArgumentError,
              "trying to register a plan service on #{self} for "\
              "#{service.task}, which is included in #{service.task.plan}"
    end

    set = (plan_services[service.task] ||= Set.new)
    set << service
    self
end

#add_trigger(query_object) {|task| ... } ⇒ Object

Add a trigger

This registers a notification: the given block will be called for each new task that match the given query object. It yields right away for the tasks that are already in the plan

Parameters:

  • query_object (#===)

    the object against which tasks are tested. Tasks for which #=== returns true are yield to the block

Yield Parameters:

  • task (Roby::Task)

    the task that matched the query object

Returns:



1199
1200
1201
1202
1203
1204
1205
1206
# File 'lib/roby/plan.rb', line 1199

def add_trigger(query_object, &block)
    tr = Trigger.new(query_object, block)
    triggers << tr
    tr.each(self) do |t|
        tr.call(t)
    end
    tr
end

#added_transaction(trsc) ⇒ Object

Hook called when a new transaction has been built on top of this plan



1227
# File 'lib/roby/plan.rb', line 1227

def added_transaction(trsc); end

#apply_replacement_operations(new_relations, removed_relations) ⇒ Object



1053
1054
1055
1056
1057
1058
1059
1060
# File 'lib/roby/plan.rb', line 1053

def apply_replacement_operations(new_relations, removed_relations)
    removed_relations.each do |graph, parent, child|
        graph.remove_relation(parent, child)
    end
    new_relations.each do |graph, parent, child, info|
        graph.add_relation(parent, child, info)
    end
end

#apply_triggers_matches(matches) ⇒ Object



323
324
325
326
327
328
329
# File 'lib/roby/plan.rb', line 323

def apply_triggers_matches(matches)
    matches.each do |trigger, matched_tasks|
        matched_tasks.each do |t|
            trigger.call(t)
        end
    end
end

#call_structure_check_handler(handler) ⇒ Object



1819
1820
1821
# File 'lib/roby/plan.rb', line 1819

def call_structure_check_handler(handler)
    handler.call(self)
end

#check_structureHash<ExecutionException,Array<Roby::Task>,nil>

Perform the structure checking step by calling the procs registered in #structure_checks and structure_checks

Returns:



1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
# File 'lib/roby/plan.rb', line 1827

def check_structure
    # Do structure checking and gather the raised exceptions
    exceptions = {}
    (Plan.structure_checks + structure_checks).each do |prc|
        new_exceptions = call_structure_check_handler(prc)
        next unless new_exceptions

        format_exception_set(exceptions, new_exceptions)
    end
    exceptions
end

#clearObject

Remove all tasks



1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
# File 'lib/roby/plan.rb', line 1682

def clear
    tasks = @tasks
    @tasks = Set.new
    free_events = @free_events
    @free_events = Set.new

    clear!

    remaining = tasks.find_all do |t|
        if t.running?
            true
        else
            finalize_task(t)
            false
        end
    end

    unless remaining.empty?
        Roby.warn "#{remaining.size} tasks remaining after clearing "\
                  "the plan as they are still running"
        remaining.each do |t|
            Roby.warn "  #{t}"
        end
    end
    free_events.each do |e|
        finalize_event(e)
    end

    self
end

#clear!Object



1672
1673
1674
1675
1676
1677
1678
1679
# File 'lib/roby/plan.rb', line 1672

def clear!
    each_task_relation_graph(&:clear)
    each_event_relation_graph(&:clear)
    @free_events.clear
    @tasks.clear
    @task_index.clear
    @task_events.clear
end

#compute_subplan_replacement(mappings, relation_graphs, child_objects: true) ⇒ 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.



1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
# File 'lib/roby/plan.rb', line 1003

def compute_subplan_replacement(mappings, relation_graphs, child_objects: true)
    mappings = mappings.dup
    mappings.compare_by_identity
    new_relations = []
    removed_relations = []
    relation_graphs.each do |graph|
        next if graph.strong?

        resolved_mappings = {}
        resolved_mappings.compare_by_identity
        mappings.each do |obj, (mapped_obj, mapped_obj_resolver)|
            next if !mapped_obj && !mapped_obj_resolver

            graph.each_in_neighbour(obj) do |parent|
                next if mappings.key?(parent)

                unless graph.copy_on_replace?
                    removed_relations << [graph, parent, obj]
                end
                unless mapped_obj
                    mapped_obj = mapped_obj_resolver.call(obj)
                    resolved_mappings[obj] = mapped_obj
                end
                new_relations << [
                    graph, parent, mapped_obj, graph.edge_info(parent, obj)
                ]
            end

            next unless child_objects

            graph.each_out_neighbour(obj) do |child|
                next if mappings.key?(child)

                unless graph.copy_on_replace?
                    removed_relations << [graph, obj, child]
                end
                unless mapped_obj
                    mapped_obj = mapped_obj_resolver.call(obj)
                    resolved_mappings[obj] = mapped_obj
                end
                new_relations << [
                    graph, mapped_obj, child, graph.edge_info(obj, child)
                ]
            end
        end
        mappings.merge!(resolved_mappings)
    end
    [new_relations, removed_relations]
end

#compute_useful_free_eventsSet<EventGenerator>

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.

Compute the set of events that are "useful" to the plan.

It contains every event that is connected to an event in #permanent_events or to an event on a task in the plan

Returns:



1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
# File 'lib/roby/plan.rb', line 1381

def compute_useful_free_events
    # Quick path for a very common case
    return Set.new if free_events.empty?

    graphs = each_event_relation_graph
             .find_all { |g| g.root_relation? && !g.weak? }

    seen = Set.new
    result = permanent_events.dup
    pending_events = free_events.to_a
    until pending_events.empty?
        # This basically computes the subplan that contains "seed" and
        # determines if it is useful or not
        seed = pending_events.shift
        next if seen.include?(seed)

        visitors = []
        graphs.each do |g|
            visitors << [
                g, UsefulFreeEventVisitor.new(
                    g, task_events, permanent_events
                ),
                [seed].to_set
            ]
            visitors << [
                g.reverse,
                UsefulFreeEventVisitor.new(
                    g.reverse, task_events, permanent_events
                ),
                [seed].to_set
            ]
        end

        component = [seed].to_set
        has_pending_seeds = true
        while has_pending_seeds
            has_pending_seeds = false
            visitors.each do |graph, visitor, seeds|
                next if seeds.empty?

                new_seeds = []
                seeds.each do |vertex|
                    next if visitor.finished_vertex?(vertex)
                    next unless graph.has_vertex?(vertex)

                    graph.depth_first_visit(vertex, visitor) do |v|
                        new_seeds << v
                    end
                end

                unless new_seeds.empty?
                    has_pending_seeds = true
                    component.merge(new_seeds)
                    visitors.each { |g, _, s| s.merge(new_seeds) if g != graph }
                end
                seeds.clear
            end
        end
        seen.merge(component)
        result.merge(component) if visitors.any? { |_, v, _| v.useful? }
    end

    result
end

#compute_useful_tasks(seeds, graphs: default_useful_task_graphs) ⇒ Set

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.

Compute the subplan that is useful for a given set of tasks

Parameters:

  • seeds (Set<Roby::Task>)

    the root "useful" tasks

  • graphs (Array<Relations::BidirectionalDirectedAdjancencyGraph>) (defaults to: default_useful_task_graphs)

    the graphs through which "usefulness" is propagated

Returns:

  • (Set)

    the set of tasks reachable from 'seeds' through the graphs



1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
# File 'lib/roby/plan.rb', line 1251

def compute_useful_tasks(seeds, graphs: default_useful_task_graphs)
    seeds = seeds.to_set
    visitors = graphs.map do |g|
        [g, RGL::DFSVisitor.new(g), seeds.dup]
    end

    result = seeds.dup

    has_queued_nodes = true
    while has_queued_nodes
        has_queued_nodes = false
        visitors.each do |graph, visitor, queue|
            next if queue.empty?

            new_queue = []
            queue.each do |vertex|
                if !visitor.finished_vertex?(vertex) && graph.has_vertex?(vertex)
                    graph.depth_first_visit(vertex, visitor) do |v|
                        yield(v) if block_given?
                        new_queue << v
                    end
                end
            end
            unless new_queue.empty?
                has_queued_nodes = true
                result.merge(new_queue)
                visitors.each { |g, _, s| s.merge(new_queue) if g != graph }
            end
            queue.clear
        end
    end

    result
end

#copy_relation_graphs_to(copy, mappings) ⇒ Object



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/roby/plan.rb', line 432

def copy_relation_graphs_to(copy, mappings)
    each_task_relation_graph do |graph|
        target_graph = copy.task_relation_graph_for(graph.class)
        graph.each_edge do |parent, child|
            target_graph.add_edge(
                mappings[parent], mappings[child], graph.edge_info(parent, child)
            )
        end
    end

    each_event_relation_graph do |graph|
        target_graph = copy.event_relation_graph_for(graph.class)
        graph.each_edge do |parent, child|
            target_graph.add_edge(
                mappings[parent], mappings[child], graph.edge_info(parent, child)
            )
        end
    end
end

#copy_task_marks(to:, from:) ⇒ Object

Apply to to the marks (permanent, mission) of from

It does not remove any marks from from



1619
1620
1621
1622
1623
# File 'lib/roby/plan.rb', line 1619

def copy_task_marks(to:, from:)
    add_permanent_task(to) if permanent_task?(from)

    add_mission_task(to) if mission_task?(from)
end

#copy_to(copy) ⇒ Object

Deprecated.

use #merge instead



255
256
257
# File 'lib/roby/plan.rb', line 255

def copy_to(copy)
    copy.merge(self)
end

#create_null_relationsObject



115
116
117
118
119
120
121
122
# File 'lib/roby/plan.rb', line 115

def create_null_relations
    @null_task_relation_graphs, @null_event_relation_graphs =
        self.class.instanciate_relation_graphs(graph_observer: graph_observer)
    @null_task_relation_graphs.freeze
    @null_task_relation_graphs.each_value(&:freeze)
    @null_event_relation_graphs.freeze
    @null_event_relation_graphs.each_value(&:freeze)
end

#create_relationsObject



124
125
126
127
128
129
130
131
132
133
134
# File 'lib/roby/plan.rb', line 124

def create_relations
    @task_relation_graphs, @event_relation_graphs =
        self.class.instanciate_relation_graphs(graph_observer: graph_observer)

    @structure_checks = []
    each_relation_graph do |graph|
        if graph.respond_to?(:check_structure)
            structure_checks << graph.method(:check_structure)
        end
    end
end

#dedupe(source) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/roby/plan.rb', line 158

def dedupe(source)
    @task_relation_graphs.each do |relation, graph|
        if relation != graph
            graph.dedupe(source.task_relation_graph_for(relation))
        end
    end
    @event_relation_graphs.each do |relation, graph|
        if relation != graph
            graph.dedupe(source.event_relation_graph_for(relation))
        end
    end
end

#deep_copyObject



374
375
376
377
378
# File 'lib/roby/plan.rb', line 374

def deep_copy
    plan = Roby::Plan.new
    mappings = deep_copy_to(plan)
    [plan, mappings]
end

#deep_copy_to(copy) ⇒ Object

Copies this plan's state (tasks, events and their relations) into the provided plan

It returns the mapping from the plan objects in self to the plan objects in copy. For instance, if t is a task in plan, then

mapping = plan.copy_to(copy)
mapping[t] => corresponding task in +copy+


388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/roby/plan.rb', line 388

def deep_copy_to(copy)
    mappings = Hash.new do |_, k|
        if !include?(k)
            raise InternalError,
                  "#{k} is listed in a relation, but is not included "\
                  "in the corresponding plan #{self}"
        else
            raise InternalError,
                  "#{k} is an object in #{self} for which no mapping "\
                  "has been created in #{copy}"
        end
    end

    # First create a copy of all the tasks
    tasks.each do |t|
        new_t = t.dup
        mappings[t] = new_t

        t.each_event do |ev|
            new_ev = ev.dup
            new_ev.instance_variable_set :@task, new_t
            new_t.bound_events[ev.symbol] = new_ev
            mappings[ev] = new_ev
        end

        copy.register_task(new_t)
        new_t.each_event do |ev|
            copy.register_event(ev)
        end
    end
    free_events.each do |e|
        new_e = e.dup
        mappings[e] = new_e
        copy.register_event(new_e)
    end

    mission_tasks.each { |t| copy.add_mission_task(mappings[t]) }
    permanent_tasks.each { |t| copy.add_permanent_task(mappings[t]) }
    permanent_events.each { |e| copy.add_permanent_event(mappings[e]) }

    copy_relation_graphs_to(copy, mappings)
    mappings
end

#default_useful_task_graphsObject

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.

Default set of graphs that should be discovered by #compute_useful_tasks



1239
1240
1241
# File 'lib/roby/plan.rb', line 1239

def default_useful_task_graphs
    each_task_relation_graph.find_all { |g| g.root_relation? && !g.weak? }
end

#dupObject



235
236
237
238
239
# File 'lib/roby/plan.rb', line 235

def dup
    new_plan = Plan.new
    copy_to(new_plan)
    new_plan
end

#each_event_relation_graph {|graph| ... } ⇒ Object

Enumerate the graph objects that contain this plan's event relation information

Yield Parameters:



205
206
207
208
209
210
211
# File 'lib/roby/plan.rb', line 205

def each_event_relation_graph
    return enum_for(__method__) unless block_given?

    event_relation_graphs.each do |k, v|
        yield(v) if k == v
    end
end

#each_object_in_transaction_stack(object) {|object| ... } ⇒ Object

Enumerate object identities along the transaction stack

The enumeration starts with the deepest transaction and stops at the topmost plan where the object is not a transaction proxy.

Parameters:

Yield Parameters:

  • object (PlanObject)

    the object's identity at the given level of the stack. Note that the last element is guaranteed to not be a transaction proxy.



2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
# File 'lib/roby/plan.rb', line 2019

def each_object_in_transaction_stack(object)
    return enum_for(__method__, object) unless block_given?

    current_plan = self
    loop do
        yield(current_plan, object)

        return unless object.transaction_proxy?

        current_plan = current_plan.plan
        object = object.__getobj__
    end
    # Never reached
end

#each_relation_graph(&block) ⇒ Object

Enumerate all graphs (event and tasks) that form this plan



194
195
196
197
198
199
# File 'lib/roby/plan.rb', line 194

def each_relation_graph(&block)
    return enum_for(__method__) unless block_given?

    each_task_relation_graph(&block)
    each_event_relation_graph(&block)
end

#each_task {|task| ... } ⇒ Object

Iterates on all tasks

Yield Parameters:



1517
1518
1519
1520
1521
# File 'lib/roby/plan.rb', line 1517

def each_task(&block)
    return enum_for(__method__) unless block_given?

    @tasks.each(&block)
end

#each_task_relation_graph {|graph| ... } ⇒ Object

Enumerate the graph objects that contain this plan's task relation information

Yield Parameters:



222
223
224
225
226
227
228
# File 'lib/roby/plan.rb', line 222

def each_task_relation_graph
    return enum_for(__method__) unless block_given?

    task_relation_graphs.each do |k, v|
        yield(v) if k == v
    end
end

#editObject



719
720
721
# File 'lib/roby/plan.rb', line 719

def edit
    yield if block_given?
end

#empty?Boolean

Returns true if there is no task in this plan

Returns:

  • (Boolean)


1510
1511
1512
# File 'lib/roby/plan.rb', line 1510

def empty?
    @tasks.empty? && @free_events.empty?
end

#event_relation_graph_for(model) ⇒ Object

Resolves an event graph object from the graph class (i.e. the graph model)



214
215
216
# File 'lib/roby/plan.rb', line 214

def event_relation_graph_for(model)
    event_relation_graphs.fetch(model)
end

#executable?Boolean

Check that this is an executable plan. This is always true for plain Plan objects and false for transcations

Returns:

  • (Boolean)


78
79
80
# File 'lib/roby/plan.rb', line 78

def executable?
    false
end

#executeObject

Calls the given block in the execution thread of this plan's engine. If there is no engine attached to this plan, yields immediately

See ExecutionEngine#execute



250
251
252
# File 'lib/roby/plan.rb', line 250

def execute
    yield
end

#finalize_event(event, timestamp = nil) ⇒ Object



1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
# File 'lib/roby/plan.rb', line 1605

def finalize_event(event, timestamp = nil)
    verify_plan_object_finalization_sanity(event)

    # Remove relations first. This is needed by transaction since
    # removing relations may need wrapping some new event, and in
    # that case these new event will be discovered as well
    event.clear_relations
    finalized_event(event)
    event.finalized!(timestamp)
end

#finalize_task(task, timestamp = nil) ⇒ Object



1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
# File 'lib/roby/plan.rb', line 1582

def finalize_task(task, timestamp = nil)
    verify_plan_object_finalization_sanity(task)
    if (services = plan_services.delete(task))
        services.each(&:finalized!)
    end

    # Remove relations first. This is needed by transaction since
    # removing relations may need wrapping some new task, and in
    # that case these new task will be discovered as well
    task.clear_relations(remove_internal: true)
    task.mission = false

    task.bound_events.each_value do |ev|
        finalized_event(ev)
    end
    finalized_task(task)

    task.bound_events.each_value do |ev|
        ev.finalized!(timestamp)
    end
    task.finalized!(timestamp)
end

#finalized_event(event) ⇒ Object

Hook called when event has been removed from this plan



1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
# File 'lib/roby/plan.rb', line 1726

def finalized_event(event)
    log(:finalized_event, droby_id, event)
    return unless event.root_object?

    transactions.each do |trsc|
        next unless trsc.proxying?

        if (proxy = trsc.find_local_object_for_event(event))
            trsc.finalized_plan_event(proxy)
        end
    end
end

#finalized_task(task) ⇒ Object

Hook called when task has been removed from this plan



1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
# File 'lib/roby/plan.rb', line 1714

def finalized_task(task)
    transactions.each do |trsc|
        next unless trsc.proxying?

        if (proxy = trsc.find_local_object_for_task(task))
            trsc.finalized_plan_task(proxy)
        end
    end
    log(:finalized_task, droby_id, task)
end

#find_all_plan_services(task) ⇒ Object

Find all the defined plan services for a given task



965
966
967
# File 'lib/roby/plan.rb', line 965

def find_all_plan_services(task)
    plan_services[task] || []
end

#find_local_tasks(*args, &block) ⇒ Object

Starts a local query on this plan.

Unlike #find_tasks, when applied on a transaction, it will only match tasks that are already in the transaction.

See #find_global_tasks for a local query.



1934
1935
1936
1937
1938
# File 'lib/roby/plan.rb', line 1934

def find_local_tasks(*args, &block)
    query = find_tasks(*args, &block)
    query.local_scope
    query
end

#find_plan_difference(other_plan, mappings) ⇒ Object

Finds a single difference between this plan and the other plan, using the provided mappings to map objects from self to object in other_plan



1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
# File 'lib/roby/plan.rb', line 1857

def find_plan_difference(other_plan, mappings)
    all_self_objects  = tasks | free_events | task_events
    all_other_objects = (
        other_plan.tasks | other_plan.free_events | other_plan.task_events
    )

    all_mapped_objects = all_self_objects.map do |obj|
        return [:new_object, obj] unless mappings.key?(obj)

        mappings[obj]
    end.to_set

    if all_mapped_objects != all_other_objects
        return [:removed_objects, all_other_objects - all_mapped_objects]
    elsif mission_tasks.map { |m| mappings[m] }.to_set != other_plan.mission_tasks
        return [:missions_differ]
    elsif permanent_tasks.map { |p| mappings[p] }.to_set != other_plan.permanent_tasks
        return [:permanent_tasks_differ]
    elsif permanent_events.map { |p| mappings[p] }.to_set != other_plan.permanent_events
        return [:permanent_events_differ]
    end

    each_task_relation_graph do |graph|
        other_graph = other_plan.task_relation_graph_for(graph.class)
        if (diff = graph.find_edge_difference(other_graph, mappings))
            return [graph.class] + diff
        end
    end

    each_event_relation_graph do |graph|
        other_graph = other_plan.event_relation_graph_for(graph.class)
        if (diff = graph.find_edge_difference(other_graph, mappings))
            return [graph.class] + diff
        end
    end
    nil
end

#find_plan_service(task) ⇒ Object

If at least one plan service is defined for task, returns one of them. Otherwise, returns nil.



971
972
973
# File 'lib/roby/plan.rb', line 971

def find_plan_service(task)
    plan_services[task]&.first
end

#find_tasks(model = nil, args = nil) ⇒ Object

Returns a Query object that applies on this plan.

This is equivalent to

Roby::Query.new(self)

Additionally, the model and args options are passed to Query#which_fullfills. For example:

plan.find_tasks(Tasks::SimpleTask, id: 20)

is equivalent to

Roby::Query.new(self).which_fullfills(Tasks::SimpleTask, id: 20)

The returned query is applied on the global scope by default. This means that, if it is applied on a transaction, it will match tasks that are in the underlying plans but not yet in the transaction, import the matches in the transaction and return the new proxies.

See #find_local_tasks for a local query.



1922
1923
1924
1925
1926
# File 'lib/roby/plan.rb', line 1922

def find_tasks(model = nil, args = nil)
    q = Queries::Query.new(self)
    q.which_fullfills(model, args) if model || args
    q
end

#find_triggers_matches(plan) ⇒ Object



317
318
319
320
321
# File 'lib/roby/plan.rb', line 317

def find_triggers_matches(plan)
    triggers.map do |tr|
        [tr, tr.each(plan).to_a]
    end
end

#force_replace(from, to) ⇒ Object



735
736
737
738
739
# File 'lib/roby/plan.rb', line 735

def force_replace(from, to)
    handle_force_replace(from, to) do
        from.replace_subplan_by(to)
    end
end

#force_replace_task(from, to) ⇒ Object



729
730
731
732
733
# File 'lib/roby/plan.rb', line 729

def force_replace_task(from, to)
    handle_force_replace(from, to) do
        from.replace_by(to)
    end
end

#format_exception_set(result, new) ⇒ 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.

Normalize the value returned by one of the #structure_checks, by computing the list of propagation parents if they were not specified in the return value

Parameters:

  • result (Hash)
  • new (Array, Hash)


1810
1811
1812
1813
1814
1815
1816
1817
# File 'lib/roby/plan.rb', line 1810

def format_exception_set(result, new)
    [*new].each do |error, tasks|
        roby_exception = error.to_execution_exception
        tasks = [error.parent] if !tasks && error.kind_of?(RelationFailedError)
        result[roby_exception] = tasks
    end
    result
end

#handle_force_replace(from, to) {|from, to| ... } ⇒ Object

Yields:



741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
# File 'lib/roby/plan.rb', line 741

def handle_force_replace(from, to)
    if !from.plan
        raise ArgumentError,
              "#{from} has been removed from plan, "\
              "cannot use as source in a replacement"
    elsif !to.plan
        raise ArgumentError,
              "#{to} has been removed from plan, "\
              "cannot use as target in a replacement"
    elsif from.plan != self
        raise ArgumentError,
              "trying to replace #{from} but its plan "\
              "is #{from.plan}, expected #{self}"
    elsif to.plan.template?
        add(to)
    elsif to.plan != self
        raise ArgumentError,
              "trying to replace #{to} but its plan "\
              "is #{to.plan}, expected #{self}"
    elsif from == to
        return
    end

    # Swap the subplans of +from+ and +to+
    yield(from, to)

    if mission_task?(from)
        add_mission_task(to)
        replaced(from, to)
        unmark_mission_task(from)
    elsif permanent_task?(from)
        add_permanent_task(to)
        replaced(from, to)
        unmark_permanent_task(from)
    else
        add(to)
        replaced(from, to)
    end
end

#handle_replace(from, to) ⇒ Object

:nodoc:



781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
# File 'lib/roby/plan.rb', line 781

def handle_replace(from, to) # :nodoc:
    handle_force_replace(from, to) do
        # Check that +to+ is valid in all hierarchy relations where
        # +from+ is a child
        unless to.fullfills?(*from.fullfilled_model)
            models = from.fullfilled_model.first
            missing = models.find_all do |m|
                !to.fullfills?(m)
            end
            if missing.empty?
                mismatching_argument =
                    from.fullfilled_model.last.find do |key, expected_value|
                        to.arguments.set?(key) &&
                            (to.arguments[key] != expected_value)
                    end
            end

            if mismatching_argument
                raise InvalidReplace.new(from, to),
                      "argument mismatch for #{mismatching_argument.first}"
            elsif !missing.empty?
                raise InvalidReplace.new(from, to),
                      "missing provided models #{missing.map(&:name).join(', ')}"
            else
                raise InvalidReplace.new(from, to),
                      "#{to} does not fullfill #{from}"
            end
        end

        # Swap the subplans of +from+ and +to+
        yield(from, to)
    end
end

#has_free_event?(generator) ⇒ Boolean

Tests whether a free event is present in this plan

Returns:

  • (Boolean)


1489
1490
1491
# File 'lib/roby/plan.rb', line 1489

def has_free_event?(generator)
    free_events.include?(generator)
end

#has_task?(task) ⇒ Boolean

Tests whether a task is present in this plan

Returns:

  • (Boolean)


1479
1480
1481
# File 'lib/roby/plan.rb', line 1479

def has_task?(task)
    tasks.include?(task)
end

#has_task_event?(generator) ⇒ Boolean

Tests whether a task event is present in this plan

Returns:

  • (Boolean)


1484
1485
1486
# File 'lib/roby/plan.rb', line 1484

def has_task_event?(generator)
    task_events.include?(generator)
end

#in_transactionObject

Creates a new transaction and yields it. Ensures that the transaction is discarded if the block returns without having committed it.



1220
1221
1222
1223
1224
# File 'lib/roby/plan.rb', line 1220

def in_transaction
    yield(trsc = Transaction.new(self))
ensure
    trsc.discard_transaction if trsc && !trsc.finalized?
end

#in_useful_subplan?(reference_task, tested_task) ⇒ Boolean

Tests whether a task is useful from the point of view of a reference task

It is O(N) where N is the number of edges in the combined task relation graphs. If you have to do a lot of tests with the same task, compute the set of useful tasks with #compute_useful_tasks

Parameters:

  • reference_task

    the reference task

  • task

    the task whose usefulness is being tested

Returns:

  • (Boolean)


2003
2004
2005
2006
2007
2008
# File 'lib/roby/plan.rb', line 2003

def in_useful_subplan?(reference_task, tested_task)
    compute_useful_tasks([reference_task]) do |useful_t|
        return true if useful_t == tested_task
    end
    false
end

#include?(object) ⇒ Boolean

Deprecated.

use the more specific #has_task?, #has_free_event? or #has_task_event? instead

Returns:

  • (Boolean)


1495
1496
1497
1498
1499
1500
1501
# File 'lib/roby/plan.rb', line 1495

def include?(object)
    Roby.warn_deprecated(
        "Plan#include? is deprecated, use one of the more specific "\
        "#has_task? #has_task_event? and #has_free_event?"
    )
    has_free_event?(object) || has_task_event?(object) || has_task?(object)
end

#inspectObject

:nodoc:



241
242
243
244
# File 'lib/roby/plan.rb', line 241

def inspect # :nodoc:
    "#<#{self}: mission_tasks=#{mission_tasks} tasks=#{tasks} "\
    "events=#{free_events} transactions=#{transactions}>"
end

#local_tasksObject



1312
1313
1314
# File 'lib/roby/plan.rb', line 1312

def local_tasks
    task_index.self_owned
end

#locally_useful_roots(with_transactions: true) ⇒ Object



1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
# File 'lib/roby/plan.rb', line 1286

def locally_useful_roots(with_transactions: true)
    # Create the set of tasks which must be kept as-is
    seeds = @task_index.mission_tasks | @task_index.permanent_tasks
    if with_transactions
        transactions.each do |trsc|
            seeds.merge trsc.proxy_tasks.keys.to_set
        end
    end
    seeds
end

#locally_useful_tasksObject



1297
1298
1299
# File 'lib/roby/plan.rb', line 1297

def locally_useful_tasks
    compute_useful_tasks(locally_useful_roots)
end

#make_useless(tasks) ⇒ Object

Ensures that the given tasks will end up being processed without forcefully stopping anything



1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
# File 'lib/roby/plan.rb', line 1326

def make_useless(tasks)
    all_tasks = compute_useful_tasks(
        Array(tasks), graphs: default_useful_task_graphs.map(&:reverse)
    ).to_set
    all_tasks.compare_by_identity
    @task_index.mission_tasks.dup.each do |t|
        unmark_mission_task(t) if all_tasks.include?(t)
    end
    @task_index.permanent_tasks.dup.each do |t|
        unmark_permanent_task(t) if all_tasks.include?(t)
    end
end

#merge(plan) ⇒ Object

Merges the content of a plan into self

It is assumed that self and plan do not intersect.

Unlike #merge!, it does not update its argument, neither update the plan objects to point to self afterwards

Parameters:

  • plan (Roby::Plan)

    the plan to merge into self



339
340
341
342
343
344
345
346
347
348
# File 'lib/roby/plan.rb', line 339

def merge(plan)
    return if plan == self

    trigger_matches = find_triggers_matches(plan)
    merging_plan(plan)
    merge_base(plan)
    merge_relation_graphs(plan)
    merged_plan(plan)
    apply_triggers_matches(trigger_matches)
end

#merge!(plan) ⇒ Object

Moves the content of other_plan into self, and clears other_plan

It is assumed that other_plan and plan do not intersect

Unlike #merge, it ensures that all plan objects have their Roby::PlanObject#plan attribute properly updated, and it cleans plan

Parameters:

  • plan (Roby::Plan)

    the plan to merge into self



358
359
360
361
362
363
364
365
366
# File 'lib/roby/plan.rb', line 358

def merge!(plan)
    return if plan == self

    tasks = plan.tasks.dup
    events = plan.free_events.dup
    tasks.each { |t| t.plan = self }
    events.each { |e| e.plan = self }
    merge(plan)
end

#merge_base(plan) ⇒ Object



259
260
261
262
263
264
265
266
267
# File 'lib/roby/plan.rb', line 259

def merge_base(plan)
    free_events.merge(plan.free_events)
    mission_tasks.merge(plan.mission_tasks)
    tasks.merge(plan.tasks)
    permanent_tasks.merge(plan.permanent_tasks)
    permanent_events.merge(plan.permanent_events)
    task_index.merge(plan.task_index)
    task_events.merge(plan.task_events)
end

#merge_relation_graphs(plan) ⇒ Object



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/plan.rb', line 269

def merge_relation_graphs(plan)
    # Now merge the relation graphs
    #
    # Since task_relation_graphs contains both Class<Graph>=>Graph and
    # Graph=>Graph, we merge only the graphs for which
    # self.task_relation_graphs has an entry (i.e. Class<Graph>) and
    # ignore the rest
    plan.task_relation_graphs.each do |rel_id, rel|
        next if rel_id == rel

        this_rel = task_relation_graphs.fetch(rel_id, nil)
        next unless this_rel

        this_rel.merge(rel)
    end
    plan.event_relation_graphs.each do |rel_id, rel|
        next if rel_id == rel

        this_rel = event_relation_graphs.fetch(rel_id, nil)
        next unless this_rel

        this_rel.merge(rel)
    end
end

#merge_transaction(transaction, merged_graphs, _added, _removed, _updated) ⇒ Object



300
301
302
303
304
305
# File 'lib/roby/plan.rb', line 300

def merge_transaction(transaction, merged_graphs, _added, _removed, _updated)
    merging_plan(transaction)
    merge_base(transaction)
    replace_relation_graphs(merged_graphs)
    merged_plan(transaction)
end

#merge_transaction!(transaction, merged_graphs, added, removed, updated) ⇒ Object



307
308
309
310
311
312
313
314
315
# File 'lib/roby/plan.rb', line 307

def merge_transaction!(transaction, merged_graphs, added, removed, updated)
    # NOTE: Task#plan= updates its bound events
    tasks = transaction.tasks.dup
    events = transaction.free_events.dup
    tasks.each { |t| t.plan = self }
    events.each { |e| e.plan = self }

    merge_transaction(transaction, merged_graphs, added, removed, updated)
end

#merged_plan(plan) ⇒ Object

Hook called when a #merge has been performed



372
# File 'lib/roby/plan.rb', line 372

def merged_plan(plan); end

#merging_plan(plan) ⇒ Object

Hook called just before performing a #merge



369
# File 'lib/roby/plan.rb', line 369

def merging_plan(plan); end

#mission?(task) ⇒ Boolean

Deprecated.

use #mission_task? instead

Returns:

  • (Boolean)


518
519
520
521
# File 'lib/roby/plan.rb', line 518

def mission?(task)
    Roby.warn_deprecated "#mission? is deprecated, use #mission_task? instead"
    mission_task?(task)
end

#mission_task?(task) ⇒ Boolean

Checks if a task is part of the plan's missions

Returns:

  • (Boolean)

See Also:



564
565
566
# File 'lib/roby/plan.rb', line 564

def mission_task?(task)
    @task_index.mission_tasks.include?(task.to_task)
end

#mission_tasksObject

The set of the robot's missions



25
26
27
# File 'lib/roby/plan.rb', line 25

def mission_tasks
    @task_index.mission_tasks
end

#move_plan_service(service, new_task) ⇒ Object

Change the actual task a given plan service is representing



956
957
958
959
960
961
962
# File 'lib/roby/plan.rb', line 956

def move_plan_service(service, new_task)
    return if new_task == service.task

    remove_plan_service(service)
    service.task = new_task
    add_plan_service(service)
end

#normalize_add_arguments(objects) ⇒ 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.

Normalize an validate the arguments to #add into a list of plan objects



471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/roby/plan.rb', line 471

def normalize_add_arguments(objects)
    objects = [objects] unless objects.respond_to?(:each)

    objects.map do |o|
        if o.respond_to?(:as_plan) then o.as_plan
        elsif o.respond_to?(:to_event) then o.to_event
        elsif o.respond_to?(:to_task) then o.to_task
        else
            raise ArgumentError,
                  "found #{o || 'nil'} which is neither a task nor an event"
        end
    end
end

#notify_event_status_change(event, status) ⇒ 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.

Perform notifications related to the status change of an event



715
716
717
# File 'lib/roby/plan.rb', line 715

def notify_event_status_change(event, status)
    log(:event_status_change, event, status)
end

#notify_task_status_change(task, status) ⇒ 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.

Perform notifications related to the status change of a task



705
706
707
708
709
710
# File 'lib/roby/plan.rb', line 705

def notify_task_status_change(task, status)
    if (services = plan_services[task])
        services.each { |s| s.notify_task_status_change(status) }
    end
    log(:task_status_change, task, status)
end

#num_eventsObject

The number of events, both free and task events



1474
1475
1476
# File 'lib/roby/plan.rb', line 1474

def num_events
    task_events.size + free_events.size
end

#num_free_eventsObject

The number of events that are not task events



1469
1470
1471
# File 'lib/roby/plan.rb', line 1469

def num_free_events
    free_events.size
end

#num_tasksObject

The number of tasks



1464
1465
1466
# File 'lib/roby/plan.rb', line 1464

def num_tasks
    tasks.size
end

#owns?(object) ⇒ Boolean

True if this plan owns the given object, i.e. if all the owners of the object are also owners of the plan.

Returns:

  • (Boolean)


725
726
727
# File 'lib/roby/plan.rb', line 725

def owns?(object)
    (object.owners - owners).empty?
end

#permanent?(object) ⇒ Boolean

Deprecated.

Returns:

  • (Boolean)


618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/roby/plan.rb', line 618

def permanent?(object)
    Roby.warn_deprecated(
        "#permanent? is deprecated, use either "\
        "#permanent_task? or #permanent_event?"
    )

    if object.respond_to?(:to_task)
        permanent_task?(object)
    elsif object.respond_to?(:to_event)
        permanent_event?(object)
    else
        raise ArgumentError, "expected a task or event and got #{object}"
    end
end

#permanent_event?(generator) ⇒ Boolean

True if the given event is registered as a permanent event on self

Returns:

  • (Boolean)


683
684
685
# File 'lib/roby/plan.rb', line 683

def permanent_event?(generator)
    @task_index.permanent_events.include?(generator)
end

#permanent_eventsObject

The list of events that are kept outside GC. Do not change that set directly, use #permanent and #auto instead.



40
41
42
# File 'lib/roby/plan.rb', line 40

def permanent_events
    @task_index.permanent_events
end

#permanent_task?(task) ⇒ Boolean

True if the given task is registered as a permanent task on self

Returns:

  • (Boolean)


649
650
651
# File 'lib/roby/plan.rb', line 649

def permanent_task?(task)
    @task_index.permanent_tasks.include?(task)
end

#permanent_tasksObject

The set of tasks that are kept around "just in case"



31
32
33
# File 'lib/roby/plan.rb', line 31

def permanent_tasks
    @task_index.permanent_tasks
end

#query_result_set(matcher) ⇒ 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.

Internal delegation from the matchers to the plan object to determine the 'right' query algorithm



1944
1945
1946
# File 'lib/roby/plan.rb', line 1944

def query_result_set(matcher)
    Queries::PlanQueryResult.from_plan(self, matcher)
end

#real_planObject

If this plan is a toplevel plan, returns self. If it is a transaction, returns the underlying plan



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

def real_plan
    ret = self
    ret = ret.plan while ret.respond_to?(:plan)
    ret
end

#recreate(task) ⇒ Object

Replace task with a fresh copy of itself.

The new task takes the place of the old one in the plan: any relation that was going to/from task or one of its events is removed, and the corresponding one is created, but this time involving the newly created task.



1745
1746
1747
1748
1749
# File 'lib/roby/plan.rb', line 1745

def recreate(task)
    new_task = task.create_fresh_copy
    replace_task(task, new_task)
    new_task
end

#refresh_relationsObject



136
137
138
139
# File 'lib/roby/plan.rb', line 136

def refresh_relations
    create_relations
    create_null_relations
end

#register_event(event) ⇒ 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.

Registers a task object in this plan

It is for Roby internal usage only, for the creation of template plans. Use #add.



1092
1093
1094
1095
1096
1097
1098
1099
# File 'lib/roby/plan.rb', line 1092

def register_event(event)
    event.plan = self
    if event.root_object?
        free_events << event
    else
        task_events << event
    end
end

#register_task(task) ⇒ 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.

Registers a task object in this plan

It is for Roby internal usage only, for the creation of template plans. Use #add.



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

def register_task(task)
    task.plan = self
    tasks << task
    task_index.add(task)
    task_events.merge(task.each_event)
end

#registered_plan_services_for(task) ⇒ Object

Whether there are services registered for the given task



951
952
953
# File 'lib/roby/plan.rb', line 951

def registered_plan_services_for(task)
    @plan_services[task] || Set.new
end

#remote_tasksObject



1316
1317
1318
1319
1320
1321
1322
# File 'lib/roby/plan.rb', line 1316

def remote_tasks
    if (local_tasks = task_index.self_owned)
        tasks - local_tasks
    else
        tasks
    end
end

#remove_fault_response_table(table) ⇒ void #remove_fault_response_table(table_model) ⇒ void

This method returns an undefined value.

Remove a fault response table that has been added with #use_fault_response_table

Overloads:

See Also:



1985
1986
1987
1988
1989
1990
1991
1992
# File 'lib/roby/plan.rb', line 1985

def remove_fault_response_table(table_model)
    active_fault_response_tables.delete_if do |t|
        if (table_model.kind_of?(Class) && t.kind_of?(table_model)) || t == table_model
            t.removed!
            true
        end
    end
end

#remove_free_event(event, timestamp = Time.now) ⇒ Object



1643
1644
1645
1646
# File 'lib/roby/plan.rb', line 1643

def remove_free_event(event, timestamp = Time.now)
    verify_plan_object_finalization_sanity(event)
    remove_free_event!(event, timestamp)
end

#remove_free_event!(event, timestamp = Time.now) ⇒ Object



1648
1649
1650
1651
1652
1653
# File 'lib/roby/plan.rb', line 1648

def remove_free_event!(event, timestamp = Time.now)
    @free_events.delete(event)
    @task_index.permanent_events.delete(event)
    finalize_event(event, timestamp)
    self
end

#remove_object(object, timestamp = Time.now) ⇒ Object

Deprecated.


1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
# File 'lib/roby/plan.rb', line 1656

def remove_object(object, timestamp = Time.now)
    Roby.warn_deprecated(
        "#remove_object is deprecated, use either "\
        "#remove_task or #remove_free_event"
    )

    if has_task?(object)
        remove_task(object, timestamp)
    elsif has_free_event?(object)
        remove_free_event(object, timestamp)
    else
        raise ArgumentError,
              "#{object} is neither a task nor a free event of #{self}"
    end
end

#remove_plan_service(service) ⇒ Object

Deregisters a plan service from this plan



943
944
945
946
947
948
# File 'lib/roby/plan.rb', line 943

def remove_plan_service(service)
    return unless (set = plan_services[service.task])

    set.delete(service)
    plan_services.delete(service.task) if set.empty?
end

#remove_task(task, timestamp = Time.now) ⇒ Object



1625
1626
1627
1628
# File 'lib/roby/plan.rb', line 1625

def remove_task(task, timestamp = Time.now)
    verify_plan_object_finalization_sanity(task)
    remove_task!(task, timestamp)
end

#remove_task!(task, timestamp = Time.now) ⇒ Object



1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
# File 'lib/roby/plan.rb', line 1630

def remove_task!(task, timestamp = Time.now)
    @tasks.delete(task)
    @task_index.mission_tasks.delete(task)
    @task_index.permanent_tasks.delete(task)
    @task_index.remove(task)

    task.bound_events.each_value do |ev|
        @task_events.delete(ev)
    end
    finalize_task(task, timestamp)
    self
end

#remove_transaction(trsc) ⇒ Object

Removes the transaction trsc from the list of known transactions built on this plan



1231
1232
1233
# File 'lib/roby/plan.rb', line 1231

def remove_transaction(trsc)
    transactions.delete(trsc)
end

#remove_trigger(trigger) ⇒ void

This method returns an undefined value.

Removes a trigger

Parameters:

  • trigger (Object)

    the trigger to be removed. This is the return value of the corresponding #add_trigger call



1213
1214
1215
1216
# File 'lib/roby/plan.rb', line 1213

def remove_trigger(trigger)
    triggers.delete(trigger)
    nil
end

#replace(from, to, filter: ReplacementFilter::Null.new) ⇒ Object

Replace from by to in the plan, in all relations in which from and its events are /children/. It therefore replaces the subplan generated by from (i.e. from and all the tasks/events that can be reached by following the task and event relations) by the subplan generated by to.

See also #replace_task



923
924
925
926
927
# File 'lib/roby/plan.rb', line 923

def replace(from, to, filter: ReplacementFilter::Null.new)
    handle_replace(from, to) do
        from.replace_subplan_by(to, filter: filter)
    end
end

#replace_relation_graphs(merged_graphs) ⇒ Object



294
295
296
297
298
# File 'lib/roby/plan.rb', line 294

def replace_relation_graphs(merged_graphs)
    merged_graphs.each do |self_g, new_g|
        self_g.replace(new_g)
    end
end

#replace_subplan(task_mappings, event_mappings, task_children: true, event_children: true) ⇒ Object

Replace subgraphs by another in the plan

It copies relations that are not within the keys in task_mappings and event_mappings to the corresponding task/events. The targets might be nil, in which case the relations involving the source will be simply ignored.

If needed, instead of providing an object as target, one can provide a resolver object which will be called with #call and the source, The resolver should be given as a second element of a pair, e.g.

source => [nil, #call]


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

def replace_subplan(
    task_mappings, event_mappings, task_children: true, event_children: true
)
    new_relations, removed_relations =
        compute_subplan_replacement(task_mappings, each_task_relation_graph,
                                    child_objects: task_children)
    apply_replacement_operations(new_relations, removed_relations)

    new_relations, removed_relations =
        compute_subplan_replacement(event_mappings, each_event_relation_graph,
                                    child_objects: event_children)
    apply_replacement_operations(new_relations, removed_relations)
end

#replace_task(from, to, filter: ReplacementFilter::Null.new) ⇒ Object

Replace the task from by to in all relations from is part of (including events).

See also #replace



910
911
912
913
914
# File 'lib/roby/plan.rb', line 910

def replace_task(from, to, filter: ReplacementFilter::Null.new)
    handle_replace(from, to) do
        from.replace_by(to, filter: filter)
    end
end

#replaced(replaced_task, replacing_task) ⇒ Object

Hook called when replacing_task has replaced replaced_task in this plan



1063
1064
1065
1066
1067
1068
1069
1070
1071
# File 'lib/roby/plan.rb', line 1063

def replaced(replaced_task, replacing_task)
    # Make the PlanService object follow the replacement
    return unless (services = plan_services.delete(replaced_task))

    services.each do |srv|
        srv.task = replacing_task
        (plan_services[replacing_task] ||= Set.new) << srv
    end
end

#replan(task) ⇒ Roby::Task

Creates a new planning pattern replacing the given task and its current planner

Parameters:

  • task (Roby::Task)

    the task that needs to be replanned

Returns:



1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
# File 'lib/roby/plan.rb', line 1756

def replan(task)
    return task.create_fresh_copy unless task.planning_task

    planner = replan(task.planning_task)
    planned = task.create_fresh_copy
    planned.abstract = true
    planned.planned_by planner
    replace(task, planned)
    planned
end

#root_plan?Boolean

True if this plan is root in the plan hierarchy

Returns:

  • (Boolean)


494
495
496
# File 'lib/roby/plan.rb', line 494

def root_plan?
    true
end

#same_plan?(other_plan, mappings) ⇒ Boolean

Compares this plan to other_plan, mappings providing the mapping from task/Events in self to task/events in other_plan

Returns:

  • (Boolean)


1897
1898
1899
# File 'lib/roby/plan.rb', line 1897

def same_plan?(other_plan, mappings)
    !find_plan_difference(other_plan, mappings)
end

#sibling_on?(peer) ⇒ Boolean

If this object is the main plan, checks if we are subscribed to the whole remote plan

Returns:

  • (Boolean)


55
56
57
58
59
60
# File 'lib/roby/plan.rb', line 55

def sibling_on?(peer)
    if Roby.plan == self then peer.remote_plan
    else
        super
    end
end

#sizeObject

Count of tasks in this plan



1504
1505
1506
1507
# File 'lib/roby/plan.rb', line 1504

def size
    Roby.warn_deprecated "Plan#size is deprecated, use #num_tasks instead"
    @tasks.size
end

#static_garbage_collect(protected_roots: Set.new, &block) ⇒ Object

Run a garbage collection pass. This is 'static', as it does not care about the task's state: it will simply remove from the plan any task that is not useful in the context of the plan.

This is mainly useful for static tests, and for transactions

Do not use it on executed plans.



1846
1847
1848
1849
1850
1851
1852
1853
# File 'lib/roby/plan.rb', line 1846

def static_garbage_collect(protected_roots: Set.new, &block)
    unneeded = unneeded_tasks(additional_useful_roots: protected_roots)
    if block
        unneeded.each(&block)
    else
        unneeded.each { |t| remove_task(t) }
    end
end

#task_relation_graph_for(model) ⇒ Object

Resolves a task graph object from the graph class (i.e. the graph model)



231
232
233
# File 'lib/roby/plan.rb', line 231

def task_relation_graph_for(model)
    task_relation_graphs.fetch(model)
end

#template?Boolean

A template plan is meant to be injected in another plan

When a Roby::PlanObject is included in a template plan, adding relations to other tasks causes the plans to merge as needed. Doing the same operation with plain plans causes an error

Returns:

  • (Boolean)

See Also:



72
73
74
# File 'lib/roby/plan.rb', line 72

def template?
    false
end

#transaction_stackArray

Returns the set of stacked transaction

Returns:

  • (Array)

    the list of plans in the transaction stack, the first element being the most-nested transaction and the last element the underlying real plan (equal to #real_plan)



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

def transaction_stack
    plan_chain = [self]
    plan_chain << plan_chain.last.plan while plan_chain.last.respond_to?(:plan)
    plan_chain
end

#unmark_mission(task) ⇒ Object

Deprecated.


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

def unmark_mission(task)
    Roby.warn_deprecated(
        "#unmark_mission is deprecated, use #unmark_mission_task instead"
    )
    unmark_mission_task(task)
end

#unmark_mission_task(task) ⇒ Object

Removes a task from the plan's missions

It does not remove the task from the plan. In a plan that is being executed, it is done by garbage collection. In a static plan, it can either be done with #static_garbage_collect or directly by calling #remove_task or #remove_free_event

See Also:



576
577
578
579
580
581
582
583
584
# File 'lib/roby/plan.rb', line 576

def unmark_mission_task(task)
    task = task.to_task
    return unless @task_index.mission_tasks.include?(task)

    @task_index.mission_tasks.delete(task)
    task.mission = false if task.self_owned?
    notify_task_status_change(task, :normal)
    self
end

#unmark_permanent(object) ⇒ Object



602
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'lib/roby/plan.rb', line 602

def unmark_permanent(object)
    Roby.warn_deprecated(
        "#unmark_permanent is deprecated, use either #unmark_permanent_task "\
        "or #unmark_permanent_event"
    )

    if object.respond_to?(:to_task)
        unmark_permanent_task(object)
    elsif object.respond_to?(:to_event)
        unmark_permanent_event(object)
    else
        raise ArgumentError, "expected a task or event and got #{object}"
    end
end

#unmark_permanent_event(event) ⇒ Object

Removes a task from the set of permanent tasks

This does not remove the event from the plan. In plans being executed, the removal will be done by garabage collection. In plans used as data structures, either use #static_garbage_collect or remove the event directly with #remove_task or #remove_free_event

See Also:



695
696
697
698
699
700
# File 'lib/roby/plan.rb', line 695

def unmark_permanent_event(event)
    if @task_index.permanent_events.delete?(event.to_event)
        notify_event_status_change(event, :normal)
    end
    nil
end

#unmark_permanent_task(task) ⇒ Object

Removes a task from the set of permanent tasks

This does not remove the event from the plan. In plans being executed, the removal will be done by garabage collection. In plans used as data structures, either use #static_garbage_collect or remove the event directly with #remove_task or #remove_free_event

See Also:



661
662
663
664
665
666
667
# File 'lib/roby/plan.rb', line 661

def unmark_permanent_task(task)
    if @task_index.permanent_tasks.delete?(task.to_task)

        notify_task_status_change(task, :normal)
    end
    nil
end

#unneeded_eventsObject

The set of events that can be removed from the plan



1453
1454
1455
1456
1457
1458
1459
1460
1461
# File 'lib/roby/plan.rb', line 1453

def unneeded_events
    useful_events = self.useful_events

    result = (free_events - useful_events)
    result.delete_if do |ev|
        transactions.any? { |trsc| trsc.find_local_object_for_event(ev) }
    end
    result
end

#unneeded_tasks(additional_useful_roots: Set.new) ⇒ Object



1308
1309
1310
# File 'lib/roby/plan.rb', line 1308

def unneeded_tasks(additional_useful_roots: Set.new)
    tasks - useful_tasks(additional_roots: additional_useful_roots)
end

#use_fault_response_table(table_model, arguments = {}) ⇒ Coordination::FaultResponseTable, void

Enables a fault response table on this plan

Parameters:

  • table_model (Model<Coordination::FaultResponseTable>)

    the fault response table model

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

    the arguments that should be passed to the created table

Returns:

See Also:



1963
1964
1965
1966
1967
1968
# File 'lib/roby/plan.rb', line 1963

def use_fault_response_table(table_model, arguments = {})
    table = table_model.new(self, arguments)
    table.attach_to(self)
    active_fault_response_tables << table
    table
end

#useful_eventsObject

Computes the set of events that are useful in the plan Events are 'useful' when they are chained to a task.



1448
1449
1450
# File 'lib/roby/plan.rb', line 1448

def useful_events
    compute_useful_free_events
end

#useful_task?(task) ⇒ Boolean

Computes the set of useful tasks and checks that task is in it. This is quite slow. It is here for debugging purposes. Do not use it in production code

Returns:

  • (Boolean)


1342
1343
1344
# File 'lib/roby/plan.rb', line 1342

def useful_task?(task)
    tasks.include?(task) && !unneeded_tasks.include?(task)
end

#useful_tasks(additional_roots: Set.new, with_transactions: true) ⇒ Object



1301
1302
1303
1304
1305
1306
# File 'lib/roby/plan.rb', line 1301

def useful_tasks(additional_roots: Set.new, with_transactions: true)
    compute_useful_tasks(
        locally_useful_roots(with_transactions: with_transactions) |
        additional_roots
    )
end

#validate_graphs(graphs) ⇒ Object

Verifies that all graphs that should be acyclic are



453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/roby/plan.rb', line 453

def validate_graphs(graphs)
    # Make a topological sort of the graphs
    seen = Set.new
    Relations.each_graph_topologically(graphs) do |g|
        next if seen.include?(g)
        next unless g.dag?
        unless g.acyclic?
            raise Relations::CycleFoundError, "#{g.class} has cycles"
        end

        seen << g
        seen.merge(g.recursive_subsets)
    end
end

#verify_plan_object_finalization_sanity(object) ⇒ 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.

Perform sanity checks on a plan object that will be finalized



1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
# File 'lib/roby/plan.rb', line 1555

def verify_plan_object_finalization_sanity(object)
    unless object.root_object?
        raise ArgumentError, "cannot remove #{object} which is a non-root object"
    end

    return if object.plan == self

    if !object.plan
        if object.removed_at && !object.removed_at.empty?
            raise ArgumentError,
                  "#{object} has already been removed from its plan\n"\
                  "Removed at\n  #{object.removed_at.join("\n  ")}"
        else
            raise ArgumentError,
                  "#{object} has already been removed from its plan. "\
                  "Set PlanObject.debug_finalization_place to true to "\
                  "get the backtrace of where (in the code) the object "\
                  "got finalized"
        end
    elsif object.plan.template?
        raise ArgumentError, "#{object} has never been included in this plan"
    else
        raise ArgumentError,
              "#{object} is not in #{self}: #plan == #{object.plan}"
    end
end