Class: Syskit::NetworkGeneration::SystemNetworkGenerator

Inherits:
Object
  • Object
show all
Extended by:
Logger::Hierarchy
Includes:
Logger::Hierarchy, Roby::DRoby::EventLogging
Defined in:
lib/syskit/network_generation/system_network_generator.rb

Overview

Generate a plan from a set of InstanceRequirement objects

It generates the canonical, non-deployed, plan. It does not take care of the adaptation of an existing plan into the generated one

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(plan, event_logger: plan.event_logger, merge_solver: MergeSolver.new(plan)) ⇒ SystemNetworkGenerator

Returns a new instance of SystemNetworkGenerator



16
17
18
19
20
21
22
23
24
25
26
# File 'lib/syskit/network_generation/system_network_generator.rb', line 16

def initialize(plan,
               event_logger: plan.event_logger,
               merge_solver: MergeSolver.new(plan))
    if merge_solver.plan != plan
        raise ArgumentError, "gave #{merge_solver} as merge solver, which applies on #{merge_solver.plan}. Was expecting #{plan}"
    end

    @plan = plan
    @event_logger = event_logger
    @merge_solver = merge_solver
end

Instance Attribute Details

#event_loggerObject (readonly)

Returns the value of attribute event_logger



13
14
15
# File 'lib/syskit/network_generation/system_network_generator.rb', line 13

def event_logger
  @event_logger
end

#merge_solverObject (readonly)

Returns the value of attribute merge_solver



14
15
16
# File 'lib/syskit/network_generation/system_network_generator.rb', line 14

def merge_solver
  @merge_solver
end

#planObject (readonly)

Returns the value of attribute plan



12
13
14
# File 'lib/syskit/network_generation/system_network_generator.rb', line 12

def plan
  @plan
end

Class Method Details

.remove_abstract_composition_optional_children(plan) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/syskit/network_generation/system_network_generator.rb', line 154

def self.remove_abstract_composition_optional_children(plan)
    # Now remove the optional, non-resolved children of compositions
    plan.find_local_tasks(AbstractComponent).abstract.each do |task|
        parent_tasks = task.each_parent_task.to_a
        parent_tasks.each do |parent_task|
            next if !parent_task.kind_of?(Syskit::Composition)
            next if parent_task.abstract?

            roles = parent_task.roles_of(task).dup
            remaining_roles = roles.find_all do |child_role|
                !(child_model = parent_task.model.find_child(child_role)) || !child_model.optional?
            end
            if remaining_roles.empty?
                parent_task.remove_child(task)
            else
                parent_task.remove_roles(task, *(roles - remaining_roles))
            end
        end
    end
end

.verify_device_allocation(plan) ⇒ Object

Verifies that all tasks that are device drivers have at least one device attached, and that the same device is not attached to more than one task in the plan

Parameters:

  • plan (Roby::Plan)

    the plan on which we are working

Raises:



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/syskit/network_generation/system_network_generator.rb', line 289

def self.verify_device_allocation(plan)
    components = plan.find_local_tasks(Syskit::Device).to_a

    # Check that all devices are properly assigned
    missing_devices = components.find_all do |t|
        t.model.each_master_driver_service.
            any? { |srv| !t.find_device_attached_to(srv) }
    end
    if !missing_devices.empty?
        raise DeviceAllocationFailed.new(plan, missing_devices),
            "could not allocate devices for the following tasks: #{missing_devices}"
    end

    devices = Hash.new
    components.each do |task|
        task.each_master_device do |dev|
            device_name = dev.full_name
            if old_task = devices[device_name]
                raise ConflictingDeviceAllocation.new(dev, task, old_task)
            else
                devices[device_name] = task
            end
        end
    end
end

.verify_no_multiplexing_connections(plan) ⇒ Object

Verifies that there are no multiple output - single input connections towards ports that are not multiplexing ports

Parameters:

  • plan (Roby::Plan)

    the plan on which we are working

Raises:

  • (SpecError)

    if some abstract tasks are still in the plan



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/syskit/network_generation/system_network_generator.rb', line 262

def self.verify_no_multiplexing_connections(plan)
    task_contexts = plan.find_local_tasks(TaskContext).to_a
    task_contexts.each do |task|
        seen = Hash.new
        task.each_concrete_input_connection do |source_task, source_port, sink_port, _|
            if (port_model = task.model.find_input_port(sink_port)) && port_model.multiplexes?
                next
            elsif seen[sink_port]
                seen_task, seen_port = seen[sink_port]
                if [source_task, source_port] != [seen_task, seen_port]
                    raise SpecError, "#{task}.#{sink_port} is connected multiple times, at least to #{source_task}.#{source_port} and #{seen_task}.#{seen_port}"
                end
            end
            seen[sink_port] = [source_task, source_port]
        end
    end
end

.verify_task_allocation(plan) ⇒ Object

Verifies that the task allocation is complete

Parameters:

  • plan (Roby::Plan)

    the plan on which we are working

Raises:



247
248
249
250
251
252
253
254
# File 'lib/syskit/network_generation/system_network_generator.rb', line 247

def self.verify_task_allocation(plan)
    components = plan.find_local_tasks(AbstractComponent)
    still_abstract = components.find_all(&:abstract?)
    if !still_abstract.empty?
        raise TaskAllocationFailed.new(self, still_abstract),
            "could not find implementation for the following abstract tasks: #{still_abstract}"
    end
end

Instance Method Details

#allocate_devices(task) ⇒ Object

Try to autoallocate the devices in task based on the information in the instance requirements in the task's hierarchy



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/syskit/network_generation/system_network_generator.rb', line 67

def allocate_devices(task)
    Engine.debug do
        Engine.debug "allocating devices on #{task} using"
        break
    end

    task.model.each_master_driver_service do |srv|
        next if task.find_device_attached_to(srv)
        if dev = find_selected_device_in_hierarchy(:"#{srv.name}_dev", task, srv.model.to_instance_requirements)
            Engine.debug do
                Engine.debug "  selected #{dev} for #{srv.name}"
            end
            task.arguments[:"#{srv.name}_dev"] = dev
        end
    end
end

#compute_system_network(instance_requirements, garbage_collect: true, validate_abstract_network: true, validate_generated_network: true) ⇒ Object

Compute in #plan the network needed to fullfill the requirements

This network is neither validated nor tied to actual deployments



178
179
180
181
182
183
184
185
186
187
188
189
190
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/syskit/network_generation/system_network_generator.rb', line 178

def compute_system_network(instance_requirements, garbage_collect: true,
                           validate_abstract_network: true,
                           validate_generated_network: true)
    toplevel_tasks = log_timepoint_group 'instanciate' do
        instanciate(instance_requirements)
    end

    merge_solver.merge_identical_tasks
    log_timepoint 'merge'
    Engine.instanciated_network_postprocessing.each do |block|
        block.call(self, plan)
        log_timepoint "postprocessing:#{block}"
    end
    link_to_busses
    log_timepoint 'link_to_busses'
    merge_solver.merge_identical_tasks
    log_timepoint 'merge'

    self.class.remove_abstract_composition_optional_children(plan)
    log_timepoint 'remove-optional'

    # Finally, select 'default' as configuration for all
    # remaining tasks that do not have a 'conf' argument set
    plan.find_local_tasks(Component).
        each do |task|
            task.freeze_delayed_arguments
        end
    log_timepoint 'default_conf'

    # Cleanup the remainder of the tasks that are of no use right
    # now (mostly devices)
    if garbage_collect
        plan.static_garbage_collect do |obj|
            debug { "  removing #{obj}" }
            # Remove tasks that we just added and are not
            # useful anymore
            plan.remove_task(obj)
        end
        log_timepoint 'static_garbage_collect'
    end

    # And get rid of the 'permanent' marking we use to be able to
    # run static_garbage_collect
    plan.each_task do |task|
        plan.unmark_permanent_task(task)
    end

    Engine.system_network_postprocessing.each do |block|
        block.call(self)
    end
    log_timepoint 'postprocessing'

    if validate_abstract_network
        self.validate_abstract_network
        log_timepoint 'validate_abstract_network'
    end
    if validate_generated_network
        self.validate_generated_network
        log_timepoint 'validate_generated_network'
    end

    toplevel_tasks
end

#find_selected_device_in_hierarchy(argument_name, leaf_task, requirements) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/syskit/network_generation/system_network_generator.rb', line 48

def find_selected_device_in_hierarchy(argument_name, leaf_task, requirements)
    _, model, _ = leaf_task.requirements.resolved_dependency_injection.selection_for(nil, requirements)
    if model && dev = model.arguments[argument_name]
        return dev
    else
        devices = Set.new
        leaf_task.each_parent_task do |parent|
            if sel = find_selected_device_in_hierarchy(argument_name, parent, requirements)
                devices << sel
            end
        end
        if devices.size == 1
            return devices.first
        end
    end
end

#generate(instance_requirements, garbage_collect: true, validate_abstract_network: true, validate_generated_network: true) ⇒ Hash<Syskit::Component=>Array<InstanceRequirements>>

Generate the network in the plan

Returns:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/syskit/network_generation/system_network_generator.rb', line 33

def generate(instance_requirements,
             garbage_collect: true,
             validate_abstract_network: true,
             validate_generated_network: true)

    # We first generate a non-deployed network that fits all
    # requirements.
    log_timepoint_group 'compute_system_network' do
        compute_system_network(instance_requirements,
                               garbage_collect: garbage_collect,
                               validate_abstract_network: validate_abstract_network,
                               validate_generated_network: validate_generated_network)
    end
end

#instanciate(instance_requirements) ⇒ void

This method returns an undefined value.

Create on #plan the task instances that are currently required in #real_plan

It does not try to merge the result, #plan is probably full of redundancies after this call



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/syskit/network_generation/system_network_generator.rb', line 91

def instanciate(instance_requirements)
    log_timepoint "instanciate_requirements"
    toplevel_tasks = instance_requirements.each_with_index.map do |requirements, i|
        task = requirements.instanciate(plan).
            to_task
        # We add all these tasks as permanent tasks, to use
        # #static_garbage_collect to cleanup #plan.
        plan.add_permanent_task(task)

        fullfilled_task_m, fullfilled_modules, fullfilled_args = requirements.fullfilled_model
        fullfilled_args = fullfilled_args.each_key.inject(Hash.new) do |h, arg_name|
            if task.arguments.set?(arg_name)
                h[arg_name] = task.arguments[arg_name]
            end
            h
        end
        task.fullfilled_model = [fullfilled_task_m, fullfilled_modules, fullfilled_args]
        log_timepoint "task-#{i}"
        task
    end
    plan.each_task do |task|
        if task.respond_to?(:each_master_driver_service)
            allocate_devices(task)
        end
    end
    log_timepoint 'device_allocation'
    Engine.instanciation_postprocessing.each do |block|
        block.call(self, plan)
        log_timepoint "postprocessing:#{block}"
    end
    toplevel_tasks
end

Creates communication busses and links the tasks to them



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/syskit/network_generation/system_network_generator.rb', line 125

def link_to_busses
    # Get all the tasks that need at least one communication bus
    candidates = plan.find_local_tasks(Syskit::Device).
        inject(Hash.new) do |h, t|
            required_busses = t.each_master_device.inject(Array.new) do |list, dev|
                list + dev.com_busses
            end.to_set
            if !required_busses.empty?
                h[t] = required_busses
            end
            h
        end

    bus_tasks = Hash.new
    candidates.each do |task, needed_busses|
        needed_busses.each do |bus_device|
            com_bus_task = bus_tasks[bus_device] ||
                bus_device.instanciate(plan)
            bus_tasks[bus_device] ||= com_bus_task

            com_bus_task = com_bus_task.component
            com_bus_task.attach(task)
            task.depends_on com_bus_task
            task.should_configure_after com_bus_task.start_event
        end
    end
    nil
end

#validate_abstract_networkObject

Validates the network generated by #compute_system_network

It performs the tests that are only needed on an abstract network, i.e. on a network in which some tasks are still abstract



319
320
321
322
# File 'lib/syskit/network_generation/system_network_generator.rb', line 319

def validate_abstract_network
    self.class.verify_no_multiplexing_connections(plan)
    super if defined? super
end

#validate_generated_networkObject

Validates the network generated by #compute_system_network



325
326
327
328
329
# File 'lib/syskit/network_generation/system_network_generator.rb', line 325

def validate_generated_network
    self.class.verify_task_allocation(plan)
    self.class.verify_device_allocation(plan)
    super if defined? super
end