Module: Syskit::Models::Component

Includes:
MetaRuby::DSLs::FindThroughMethodMissing, MetaRuby::ModelAsClass, DataService, Base
Included in:
Component, Composition, TaskContext
Defined in:
lib/syskit/models/component.rb

Overview

Definition of model-level methods for the Component models. See the documentation of Model for an explanation of this.

Constant Summary collapse

PROVIDES_ARGUMENTS =
{ as: nil, slave_of: nil }

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from DataService

#using_data_service?

Methods included from Base

#dependency_injection_names, #pretty_print, #short_name, #to_instance_requirements, #to_s

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args, &block) ⇒ Object



720
721
722
723
724
725
# File 'lib/syskit/models/component.rb', line 720

def method_missing(m, *args, &block)
    if m == :orogen_model
        raise NoMethodError, "tried to use a method to access an oroGen model, but none exists on #{self}"
    end
    super
end

Instance Attribute Details

#concrete_modelObject

If this model is specialized, returns the most derived model that is non-specialized. Otherwise, returns self.



800
801
802
803
804
805
# File 'lib/syskit/models/component.rb', line 800

def concrete_model
    if @concrete_model
        return @concrete_model
    else return self
    end
end

Instance Method Details

#all_data_serviceArray<String,BoundDataService>

The union, along the class hierarchy, of all the values stored in data_service

Returns:



25
# File 'lib/syskit/models/component.rb', line 25

inherited_attribute(:data_service, :data_services, map: true) { Hash.new }

#all_dynamic_serviceArray<String,DynamicDataService>

The union, along the class hierarchy, of all the values stored in dynamic_service

Returns:



457
# File 'lib/syskit/models/component.rb', line 457

inherited_attribute('dynamic_service', 'dynamic_services', map: true) { Hash.new }

#all_stub_moduleArray<Module>

The union, along the class hierarchy, of all the values stored in stub_module

Returns:



34
# File 'lib/syskit/models/component.rb', line 34

inherited_attribute(:stub_module, :stub_modules) { [Module.new] }

#apply_missing_dynamic_services_from(from, specialize_if_needed = true) ⇒ Object



1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
# File 'lib/syskit/models/component.rb', line 1060

def apply_missing_dynamic_services_from(from, specialize_if_needed = true)
    missing_services = from.each_data_service.find_all do |_, srv|
        !find_data_service(srv.full_name)
    end

    if !missing_services.empty?
        # We really really need to specialize self. The reason is
        # that self.model, even though it has private
        # specializations, might be a reusable model from the system
        # designer's point of view. With the singleton class, we
        # know that it is not
        base_model = if specialize_if_needed then specialize
                     else self
                     end
        missing_services.each do |_, srv|
            dynamic_service_options = Hash[as: srv.name].
                merge(srv.dynamic_service_options)
            base_model.require_dynamic_service srv.dynamic_service.name, dynamic_service_options
        end
        base_model
    else self
    end
end

#as(service_model) ⇒ Object

Returns a view of this component as a producer of the given model

This will fail if multiple services offer service_model. In this case, one would have to first explicitely select the service and only then call #as on the returned BoundDataService object



237
238
239
240
241
242
243
# File 'lib/syskit/models/component.rb', line 237

def as(service_model)
    srv = find_data_service_from_type(service_model)
    if !srv
        raise ArgumentError, "no service of #{self} provides #{service_model}"
    end
    return srv.as(service_model)
end

#as_planObject

Returns a placeholder task that can be used to require that a task from this component model is deployed and started at a certain point in the plan.

It is usually used implicitely with the plan and relation methods directly:

cmp = task.depends_on(Cmp::MyComposition)

calls this method behind the scenes.



822
823
824
# File 'lib/syskit/models/component.rb', line 822

def as_plan
    Syskit::InstanceRequirementsTask.subplan(self)
end

#bind(object) ⇒ Roby::Task

Return a representation of an instance that is compatible with self

Returns:

  • (Roby::Task)

    task if it matches self

Raises:

  • (ArgumentError)

    if task does not fullfill self

See Also:



841
842
843
844
845
846
847
# File 'lib/syskit/models/component.rb', line 841

def bind(object)
    if component = try_bind(object)
        component
    else
        raise ArgumentError, "cannot bind #{self} to #{object}"
    end
end

#can_merge?(target_model) ⇒ Boolean

Returns:

  • (Boolean)


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
1052
1053
1054
1055
1056
1057
1058
# File 'lib/syskit/models/component.rb', line 1020

def can_merge?(target_model)
    self_real_model = concrete_model
    target_real_model = target_model.concrete_model

    if self_real_model != self || target_real_model != target_model
        if !self_real_model.can_merge?(target_real_model)
            return false
        end
    elsif !super
        return false
    end

    # Verify that we don't have collisions in the instantiated
    # dynamic services
    each_data_service do |_, self_srv|
        task_srv = target_model.find_data_service(self_srv.name)
        next if !task_srv

        if task_srv.model != self_srv.model
            NetworkGeneration::MergeSolver.debug do
                "rejecting #{self}.merge(#{target_model}): dynamic service #{self_srv.name} is of model #{self_srv.model.short_name} on #{self} and of model #{task_srv.model.short_name} on #{target_model}"
            end
            return false
        elsif task_srv.dynamic? && self_srv.dynamic?
            if task_srv.dynamic_service_options != self_srv.dynamic_service_options
                NetworkGeneration::MergeSolver.debug do
                    "rejecting #{self}.merge(#{target_model}): dynamic service #{self_srv.name} has options #{task_srv.dynamic_service_options} on self and #{self_srv.dynamic_service_options} on the candidate task"
                end
                return false
            end
        elsif task_srv.dynamic? || self_srv.dynamic?
            NetworkGeneration::MergeSolver.debug do
                "rejecting #{self}.merge(#{target_model}): #{self_srv.name} is a dynamic service on the receiver, but a static one on the target"
            end
            return false
        end
    end
    return true
end

#clear_modelObject



36
37
38
39
40
41
42
43
44
45
# File 'lib/syskit/models/component.rb', line 36

def clear_model
    super
    data_services.clear
    dynamic_services.clear
    # Note: the placeholder_models cache is cleared separately. The
    # reason is that we need to clear it on permanent and
    # non-permanent models alike, including component models that
    # are defined in syskit. The normal procedure is to call
    # #clear_model only on the models defined in the app(s)
end

#component_model?Boolean

Wether this model truly represents a component model

Returns:

  • (Boolean)

See Also:



955
956
957
# File 'lib/syskit/models/component.rb', line 955

def component_model?
    true
end

#compute_port_mappings(service_model, explicit_mappings = Hash.new) ⇒ Object

Compute the port mapping from the interface of 'service' onto the ports of 'self'

The returned hash is

service_interface_port_name => task_model_port_name


322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/syskit/models/component.rb', line 322

def compute_port_mappings(service_model, explicit_mappings = Hash.new)
    normalized_mappings = Hash.new
    explicit_mappings.each do |from, to|
        from = from.to_s if from.kind_of?(Symbol)
        to   = to.to_s   if to.kind_of?(Symbol)
        if !from.respond_to?(:to_str)
            raise ArgumentError, "unexpected value given in port mapping: #{from}, expected a string"
        elsif !to.respond_to?(:to_str)
            raise ArgumentError, "unexpected value given in port mapping: #{to}, expected a string"
        else
            normalized_mappings[from] = to
        end
    end

    # This is used later to verify that we don't automatically map
    # two different ports to the same port. It can be done
    # explicitly, though
    mapped_to_original = Hash.new { |h, k| h[k] = Array.new }

    result = Hash.new
    service_model.each_output_port do |port|
        if mapped_name = find_directional_port_mapping('output', port, normalized_mappings[port.name])
            result[port.name] = mapped_name
            mapped_to_original[mapped_name] << port.name
        else
            raise InvalidPortMapping, "cannot find an equivalent output port for #{port.name}[#{port.type_name}] on #{short_name}"
        end
    end
    service_model.each_input_port do |port|
        if mapped_name = find_directional_port_mapping('input', port, normalized_mappings[port.name])
            result[port.name] = mapped_name
            mapped_to_original[mapped_name] << port.name
        else
            raise InvalidPortMapping, "cannot find an equivalent input port for #{port.name}[#{port.type_name}] on #{short_name}"
        end
    end

    # Verify that we don't automatically map two different ports to
    # the same port
    mapped_to_original.each do |mapped, original|
        if original.size > 1
            not_explicit = original.find_all { |pname| !normalized_mappings.has_key?(pname) }
            if !not_explicit.empty?
                raise InvalidPortMapping, "automatic port mapping would map ports #{original.sort.join(", ")} to the same port #{mapped}. I refuse to do this. If you actually mean to do it, provide the mapping #{original.map { |o| "\"#{o}\" => \"#{mapped}\"" }.join(", ")} explicitly"
            end
        end
    end

    result
end

#concrete_model?Boolean

Returns true if this model is a “true” concrete model or a specialized one

Returns:

  • (Boolean)


809
810
811
# File 'lib/syskit/models/component.rb', line 809

def concrete_model?
    concrete_model == self
end

#connected?(out_port, in_port) ⇒ Boolean

Delegated call from Port#connected?

Always returns false as “plain” component ports cannot be connected

Returns:

  • (Boolean)


867
868
869
# File 'lib/syskit/models/component.rb', line 867

def connected?(out_port, in_port)
    false
end

#create_dynamic_instantiation_context(name, dynamic_service, **options) ⇒ 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.

Creation of a DynamicDataService instantiation context

DynamicDataService#instantiate delegates to this method to create the context in which the dynamic service setup block should be evaluated. It allows subclasses to provide specific additional APIs



564
565
566
# File 'lib/syskit/models/component.rb', line 564

def create_dynamic_instantiation_context(name, dynamic_service, **options)
    DynamicDataService::InstantiationContext.new(self, name, dynamic_service, **options)
end

#create_private_specializationObject

Called by #specialize to create the composition model that will be used for a private specialization



750
751
752
# File 'lib/syskit/models/component.rb', line 750

def create_private_specialization
    new_submodel
end

#create_proxy_taskObject

Create a Roby task that can be used as a placeholder for self in the plan

The returned task is always marked as abstract



939
940
941
942
943
# File 'lib/syskit/models/component.rb', line 939

def create_proxy_task
    task = new
    task.abstract = true
    task
end

#create_proxy_task_model(service_models, as: nil, extension: Placeholder) ⇒ Object

Deprecated.

use Models::Placeholder.create_for instead



924
925
926
927
# File 'lib/syskit/models/component.rb', line 924

def create_proxy_task_model(service_models, as: nil, extension: Placeholder)
    Roby.warn_deprecated "Component.create_proxy_task_model is deprecated, use Syskit::Models::Placeholder.create_for instead"
    extension.create_for(service_models, component_model: self, as: as)
end

#data_serviceHash<String,BoundDataService>

The data services defined on this task, as a mapping from the data service full name to the BoundDataService object.

Returns:



25
# File 'lib/syskit/models/component.rb', line 25

inherited_attribute(:data_service, :data_services, map: true) { Hash.new }

#data_servicesHash<String,BoundDataService>

The data services defined on this task, as a mapping from the data service full name to the BoundDataService object.

Returns:



25
# File 'lib/syskit/models/component.rb', line 25

inherited_attribute(:data_service, :data_services, map: true) { Hash.new }

#deregister_placeholder_model(placeholder_m) ⇒ 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.

Deregister a placeholder model



898
899
900
901
902
903
904
905
906
907
# File 'lib/syskit/models/component.rb', line 898

def deregister_placeholder_model(placeholder_m)
    key = placeholder_m.proxied_data_service_models.to_set
    if by_type = placeholder_models.delete(key)
        by_type.delete_if { |_, m| m == placeholder_m }
        if !by_type.empty?
            placeholder_models[key] = by_type
        end
        true
    end
end

#deregister_submodels(set) ⇒ Object

Clears all registered submodels



910
911
912
913
914
915
916
917
918
919
920
921
# File 'lib/syskit/models/component.rb', line 910

def deregister_submodels(set)
    super

    if @placeholder_models
        set.each do |m|
            if m.placeholder?
                deregister_placeholder_model(m)
            end
        end
    end
    true
end

#driver_for(model, arguments = Hash.new, &block) ⇒ Object

Declares that this task context model can be used as a driver for the device model.

It will create the corresponding device model if it does not already exist, and return it. See the documentation of Component.data_service for the description of arguments



702
703
704
705
706
# File 'lib/syskit/models/component.rb', line 702

def driver_for(model, arguments = Hash.new, &block)
    dserv = provides(model, arguments)
    argument "#{dserv.name}_dev"
    dserv
end

#dynamic_service(model, as: nil, addition_requires_reconfiguration: true, remove_when_unused: true, **backward) { ... } ⇒ Object

Declares that this component model can dynamically extend its interface by adding services of the given type

This only models the functionality of dynamically creating new services the actual related component setup needs to be done by overloading the component's #configure method.

Examples:

class Example < Syskit::Composition
  dynamic_service CameraSrv, as: 'camera' do
    provides WeirdCameraSrv, 'image_samples' => '#{name}_samples'
  end

  def configure
    super
    each_instantiated_dynamic_service('camera') do |bound_service|
      # setup the task to create the required service
    end
  end

Parameters:

  • arguments (Hash)

    a customizable set of options

Yields:

  • block that is evaluated to instantiate the service. It should call #provides with a data service that provides model (or model itself). The required data service name is accessible through the 'name' instance variable

Yield Returns:



457
# File 'lib/syskit/models/component.rb', line 457

inherited_attribute('dynamic_service', 'dynamic_services', map: true) { Hash.new }

#dynamic_servicesHash<String,DynamicDataService>

The set of dynamic services instantiated with #dynamic_service

Returns:



457
# File 'lib/syskit/models/component.rb', line 457

inherited_attribute('dynamic_service', 'dynamic_services', map: true) { Hash.new }

#each_com_bus_driver_service {|Model<ComBus>| ... } ⇒ void

This method returns an undefined value.

Enumerate all the combus that are defined on this component model

Yields:

  • (Model<ComBus>)

    com_bus_model



66
67
68
69
70
71
72
73
# File 'lib/syskit/models/component.rb', line 66

def each_com_bus_driver_service(&block)
    return enum_for(:each_com_bus_driver_service) if !block_given?
    each_root_data_service do |srv|
        if srv.model < Syskit::ComBus
            yield(srv)
        end
    end
end

#each_data_service {|element| ... } ⇒ Object

Enumerates all objects registered in data_service

Yields:

  • (element)

Yield Parameters:

Returns:



25
# File 'lib/syskit/models/component.rb', line 25

inherited_attribute(:data_service, :data_services, map: true) { Hash.new }

#each_dynamic_service {|element| ... } ⇒ Object

Enumerates all objects registered in dynamic_service

Yields:

  • (element)

Yield Parameters:

Returns:



457
# File 'lib/syskit/models/component.rb', line 457

inherited_attribute('dynamic_service', 'dynamic_services', map: true) { Hash.new }

#each_input_portObject



569
# File 'lib/syskit/models/component.rb', line 569

def each_input_port; end

#each_master_driver_service {|device_model| ... } ⇒ void

This method returns an undefined value.

Enumerate all the devices that are defined on this component model

Yield Parameters:

  • device_model (Model<Device>)


52
53
54
55
56
57
58
59
# File 'lib/syskit/models/component.rb', line 52

def each_master_driver_service(&block)
    return enum_for(:each_master_driver_service) if !block_given?
    each_root_data_service do |srv|
        if srv.model < Syskit::Device
            yield(srv)
        end
    end
end

#each_output_portObject



570
# File 'lib/syskit/models/component.rb', line 570

def each_output_port; end

#each_portObject



568
# File 'lib/syskit/models/component.rb', line 568

def each_port; end

#each_required_dynamic_service {|srv| ... } ⇒ Object

Enumerates the services that have been created from a dynamic service using #require_dynamic_service

Yield Parameters:



509
510
511
512
513
514
515
516
# File 'lib/syskit/models/component.rb', line 509

def each_required_dynamic_service
    return enum_for(:each_required_dynamic_service) if !block_given?
    each_data_service do |_, srv|
        if srv.dynamic?
            yield(srv)
        end
    end
end

#each_required_model {|concrete_model| ... } ⇒ Object

Yields:



1112
1113
1114
1115
# File 'lib/syskit/models/component.rb', line 1112

def each_required_model
    return enum_for(:each_required_model) if !block_given?
    yield(concrete_model)
end

#each_root_data_service {|Models::BoundDataService| ... } ⇒ Object

Enumerates all services that are root (i.e. not slave of other services)



154
155
156
157
158
159
160
161
# File 'lib/syskit/models/component.rb', line 154

def each_root_data_service(&block)
    return enum_for(:each_root_data_service) if !block_given?
    each_data_service(nil) do |name, service|
        if service.master?
            yield(service)
        end
    end
end

#each_slave_data_service(master_service) {|Models::BoundDataService| ... } ⇒ Object

Enumerates all services that are slave (i.e. not slave of other services)



141
142
143
144
145
146
147
148
# File 'lib/syskit/models/component.rb', line 141

def each_slave_data_service(master_service)
    return enum_for(:each_slave_data_service, master_service) if !block_given?
    each_data_service(nil) do |name, service|
        if service.master && (service.master.full_name == master_service.full_name)
            yield(service)
        end
    end
end

#each_stub_module {|element| ... } ⇒ Object

Enumerates all objects registered in stub_module

Yields:

  • (element)

Yield Parameters:

Returns:



34
# File 'lib/syskit/models/component.rb', line 34

inherited_attribute(:stub_module, :stub_modules) { [Module.new] }

#ensure_model_is_specializedModel<Component>

Makes sure this is a private specialized model

Returns:

  • (Model<Component>)

    calls #specialize, and returns the new model, only if self is not already a private specialization. Otherwise, returns self.



788
789
790
791
792
793
# File 'lib/syskit/models/component.rb', line 788

def ensure_model_is_specialized
    if private_specialization?
        return self
    else return specialize
    end
end

#find_all_data_services_from_type(type) ⇒ Array<Models::BoundDataService>

Finds a single service that provides type

Parameters:

Returns:

See Also:



293
294
295
296
297
298
299
# File 'lib/syskit/models/component.rb', line 293

def find_all_data_services_from_type(type)
    result = []
    each_data_service do |_, m|
        result << m.as(type) if m.fullfills?(type)
    end
    result
end

#find_data_service_from_type(type) ⇒ Models::BoundDataService?

Finds a single service that provides type

Parameters:

Returns:

Raises:

  • AmbiguousServiceSelection if multiple services exist with that type

See Also:



276
277
278
279
280
281
282
283
284
# File 'lib/syskit/models/component.rb', line 276

def find_data_service_from_type(type)
    candidates = find_all_data_services_from_type(type)
    if candidates.size > 1
        raise AmbiguousServiceSelection.new(self, type, candidates),
            "multiple services match #{type.short_name} on #{short_name}"
    elsif candidates.size == 1
        return candidates.first
    end
end

#find_directional_port_mapping(direction, port, expected_name) ⇒ String?

Finds the port of self that should be used for a service port 'port'

Parameters:

  • direction (String)

    it is 'input' or 'output' and caracterizes the direction of port

  • port (Orocos::Spec::Port)

    the port to be mapped

  • expected_name (String, nil)

    if not nil, it is an explicitly given port name for the component port

Returns:

  • (String, nil)

    the name of the port of self that should be used to map 'port'. It returns nil if there are no matching ports.

Raises:

  • InvalidPortMapping if expected_name is given but it is not a port of self, or not a port with the expected direction

  • InvalidPortMapping if expected_name is given but the corresponding port has a wrong type

  • InvalidPortMapping if expected_name was nil, no port exists on self with the same name than port and there are multiple ports with the same type than port



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
# File 'lib/syskit/models/component.rb', line 392

def find_directional_port_mapping(direction, port, expected_name)
    port_name = expected_name || port.name
    component_port = send("find_#{direction}_port", port_name)

    if component_port && component_port.type == port.type
        return port_name
    elsif expected_name
        if !component_port
            known_ports = send("each_#{direction}_port").
                map { |p| "#{p.name}[#{p.type.name}]" }
            raise InvalidPortMapping, "the provided port mapping from #{port.name} to #{port_name} is invalid: #{port_name} is not a #{direction} port in #{short_name}. Known output ports are #{known_ports.sort.join(", ")}"
        else
            raise InvalidPortMapping, "the provided port mapping from #{port.name} to #{port_name} is invalid: #{port_name} is of type #{component_port.type_name} in #{short_name} and I was expecting #{port.type}"
        end
    end

    candidates = send("each_#{direction}_port").
        find_all { |p| p.type == port.type }
    if candidates.empty?
        return
    elsif candidates.size == 1
        return candidates.first.name
    else
        raise InvalidPortMapping, "there are multiple candidates to map #{port.name}[#{port.type_name}]: #{candidates.map(&:name).sort.join(", ")}"
    end
end

#find_input_port(name) ⇒ Object



571
# File 'lib/syskit/models/component.rb', line 571

def find_input_port(name); end

#find_matching_service(target_model, pattern = nil) ⇒ Object

Generic data service selection method, based on a service type and an optional service name. It implements the following algorithm:

* only services that match +target_model+ are considered
* if there is only one service of that type and no pattern is
  given, that service is returned
* if there is a pattern given, it must be either the service
  full name or its subname (for slaves)
* if an ambiguity is found between root and slave data
  services, and there is only one root data service matching,
  that data service is returned.


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/syskit/models/component.rb', line 87

def find_matching_service(target_model, pattern = nil)
    # Find services in +child_model+ that match the type
    # specification
    matching_services = find_all_services_from_type(target_model)

    if pattern # match by name too
        # Find the selected service. There can be shortcuts, so
        # for instance bla.left would be able to select both the
        # 'left' main service or the 'bla.blo.left' slave
        # service.
        rx = /(^|\.)#{pattern}$/
        matching_services.delete_if { |service| service.full_name !~ rx }
    end

    if matching_services.size > 1
        main_matching_services = matching_services.
            find_all { |service| service.master? }

        if main_matching_services.size != 1
            raise AmbiguousServiceSelection.new(self, target_model, main_matching_services), "there is more than one service of type #{target_model.name} in #{self.name}#{" matching name hint #{pattern}" if pattern}"
        end
        selected = main_matching_services.first
    else
        selected = matching_services.first
    end

    selected
end

#find_output_port(name) ⇒ Object



572
# File 'lib/syskit/models/component.rb', line 572

def find_output_port(name); end

#find_placeholder_model(service_models, placeholder_type = Placeholder) ⇒ 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.

Find an existing placeholder model based on self for the given service models



880
881
882
883
884
# File 'lib/syskit/models/component.rb', line 880

def find_placeholder_model(service_models, placeholder_type = Placeholder)
    if by_type = placeholder_models[service_models]
        by_type[placeholder_type]
    end
end

#find_port(name) ⇒ Object



573
# File 'lib/syskit/models/component.rb', line 573

def find_port(name); end

#find_through_method_missing(m, args) ⇒ Object



713
714
715
716
# File 'lib/syskit/models/component.rb', line 713

def find_through_method_missing(m, args)
    MetaRuby::DSLs.find_through_method_missing(
        self, m, args, '_srv'.freeze => :find_data_service) || super
end

#fullfills?(object) ⇒ Boolean

Returns:

  • (Boolean)


985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
# File 'lib/syskit/models/component.rb', line 985

def fullfills?(object)
    if !object.respond_to?(:each_required_dynamic_service)
        return super
    end

    self_real_model   = concrete_model
    object_real_model =
        if object.respond_to?(:concrete_model)
            object.concrete_model
        else object
        end

    if self_real_model == self
        return super
    elsif !self_real_model.fullfills?(object_real_model)
        return false
    elsif !object.respond_to?(:each_required_dynamic_service)
        return true
    end

    # We've checked the public interface, Verify that we also have all
    # dynamic services instanciated in 'object'
    object.each_required_dynamic_service do |object_srv|
        self_srv = find_data_service(object_srv.name)
        if !self_srv
            return false
        elsif !self_srv.dynamic?
            return false
        elsif !self_srv.same_service?(object_srv)
            return false
        end
    end
    return true
end

#has_through_method_missing?(m) ⇒ Boolean

Returns:

  • (Boolean)


708
709
710
711
# File 'lib/syskit/models/component.rb', line 708

def has_through_method_missing?(m)
    MetaRuby::DSLs.has_through_method_missing?(
        self, m, '_srv'.freeze => :find_data_service) || super
end

#if_already_presentInstanceRequirements

Optional dependency injection

Returns an InstanceRequirements that you can use to inject optional dependencies that will be fullfilled only if there is already a matching task deployed in the plan

This can only be meaningfully used when injected for a composition's optional child



201
202
203
# File 'lib/syskit/models/component.rb', line 201

def if_already_present
    to_instance_requirements.if_already_present
end

#implicit_fullfilled_modelObject



768
769
770
771
772
773
774
775
776
777
778
779
780
781
# File 'lib/syskit/models/component.rb', line 768

def implicit_fullfilled_model
    if !@implicit_fullfilled_model
        has_abstract = false
        @implicit_fullfilled_model =
                super.find_all do |m|
                    has_abstract ||= (m == AbstractComponent)
                    !m.respond_to?(:private_specialization?) ||
                        !m.private_specialization?
                end
        @implicit_fullfilled_model << AbstractComponent \
            unless has_abstract
    end
    @implicit_fullfilled_model
end

#instanciate(plan, context = DependencyInjectionContext.new, task_arguments: Hash.new, **arguments) ⇒ Object

Generic instanciation of a component.

It creates a new task from the component model using Component.new, adds it to the plan and returns it.



167
168
169
170
# File 'lib/syskit/models/component.rb', line 167

def instanciate(plan, context = DependencyInjectionContext.new, task_arguments: Hash.new, **arguments)
    plan.add(task = new(task_arguments))
    task
end

#instanciate_dynamic_input_port(name, type, port) ⇒ Port

Adds a new port to this model based on a known dynamic port

Parameters:

  • name (String)

    the new port's name

  • port (Orocos::Spec::DynamicInputPort)

    the port model, as returned for instance by Orocos::Spec::TaskContext#find_dynamic_input_ports

Returns:

  • (Port)

    the new port's model



965
966
967
968
969
970
# File 'lib/syskit/models/component.rb', line 965

def instanciate_dynamic_input_port(name, type, port)
    orogen_model = Models.create_orogen_task_context_model
    orogen_model.input_ports[name] = port.instanciate(name, type)
    Syskit::Models.merge_orogen_task_context_models(self.orogen_model, [orogen_model])
    find_input_port(name)
end

#instanciate_dynamic_output_port(name, type, port) ⇒ Port

Adds a new port to this model based on a known dynamic port

Parameters:

  • name (String)

    the new port's name

  • port (Orocos::Spec::DynamicOutputPort)

    the port model, as returned for instance by Orocos::Spec::TaskContext#find_dynamic_output_ports

Returns:

  • (Port)

    the new port's model



978
979
980
981
982
983
# File 'lib/syskit/models/component.rb', line 978

def instanciate_dynamic_output_port(name, type, port)
    orogen_model = Models.create_orogen_task_context_model
    orogen_model.output_ports[name] = port.instanciate(name, type)
    Syskit::Models.merge_orogen_task_context_models(self.orogen_model, [orogen_model])
    find_output_port(name)
end

#merge(other_model) ⇒ Object

Returns the component model that is the merge model of self and the given other model

It will return self or other_model if they are “plain” models. In case other_model is a placeholder task model, the corresponding data service mappings will be computed and either self or another placeholder task model will be returned



1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
# File 'lib/syskit/models/component.rb', line 1091

def merge(other_model)
    if other_model.kind_of?(Syskit::Models::BoundDataService)
        return other_model.merge(self)
    elsif other_model.placeholder?
        return other_model.merge(self)
    end

    if self <= other_model
        return self
    elsif other_model <= self
        return other_model
    elsif other_model.private_specialization? || private_specialization?
        base_model = result = concrete_model.merge(other_model.concrete_model)
        result = base_model.apply_missing_dynamic_services_from(self, true)
        return result.apply_missing_dynamic_services_from(other_model, base_model == result)

    else
        raise IncompatibleComponentModels.new(self, other_model), "models #{short_name} and #{other_model.short_name} are not compatible"
    end
end

#merge_service_model(service_model, port_mappings) ⇒ Object



1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
# File 'lib/syskit/models/component.rb', line 1121

def merge_service_model(service_model, port_mappings)
    service_model.each_input_port do |p|
        self_name = port_mappings[p.name] || p.name
        self_p = find_input_port(self_name)
        if !self_p
            raise InvalidPortMapping, "#{self} cannot dynamically create ports"
        elsif p.type != self_p.type
            raise InvalidPortMapping, "#{self} already has a port named #{self_name} of type #{self_p.type}, cannot dynamically map #{p} onto it"
        end
    end

    service_model.each_output_port do |p|
        self_name = port_mappings[p.name] || p.name
        self_p = find_output_port(self_name)
        if !self_p
            raise InvalidPortMapping, "#{self} cannot dynamically create ports"
        elsif p.type != self_p.type
            raise InvalidPortMapping, "#{self} already has a port named #{self_name} of type #{self_p.type}, cannot dynamically map #{p} onto it"
        end
    end
end

#needs_stub?(component) ⇒ Boolean

Checks if a given component implementation needs to be stubbed

Returns:

  • (Boolean)


133
134
135
# File 'lib/syskit/models/component.rb', line 133

def needs_stub?(component)
    false
end

#placeholder?Boolean

Wether this model represents a placeholder for data services

Returns:

  • (Boolean)

See Also:



948
949
950
# File 'lib/syskit/models/component.rb', line 948

def placeholder?
    false
end

#port_mappings_for(model) ⇒ Object

Defined to be compatible, in port mapping code, with the data services



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/syskit/models/component.rb', line 251

def port_mappings_for(model)
    if model.kind_of?(Class)
        if fullfills?(model)
            mappings = Hash.new
            model.each_port do |port|
                mappings[port.name] = port.name
            end
            mappings
        else
            raise ArgumentError, "#{model.short_name} is not fullfilled by #{self}"
        end
    else
        find_data_service_from_type(model).port_mappings_for_task
    end
end

#port_mappings_for_taskObject

Defined to be compatible, in port mapping code, with the data services



246
247
248
# File 'lib/syskit/models/component.rb', line 246

def port_mappings_for_task
    Hash.new { |h,k| k }
end

#prefer_deployed_tasks(*selections) ⇒ Object



211
212
213
# File 'lib/syskit/models/component.rb', line 211

def prefer_deployed_tasks(*selections)
    to_instance_requirements.prefer_deployed_tasks(*selections)
end

#prepare_stub(component) ⇒ Object

Apply what's necessary for this component (from the underlying component implementation) to be a proper component stub



125
126
127
128
129
130
# File 'lib/syskit/models/component.rb', line 125

def prepare_stub(component)
    stub_modules = each_stub_module.to_a
    stub_modules.each do |m|
        component.orocos_task.extend m
    end
end

#private_specialization=(value) ⇒ Model<TaskContext>

If true, this model is used internally as specialization of another component model (as e.g. to represent dynamic service instantiation). Otherwise, it is an actual component model.

Returns:



740
# File 'lib/syskit/models/component.rb', line 740

attr_predicate :private_specialization?, true

#private_specialization?Model<TaskContext>

If true, this model is used internally as specialization of another component model (as e.g. to represent dynamic service instantiation). Otherwise, it is an actual component model.

Returns:



740
# File 'lib/syskit/models/component.rb', line 740

attr_predicate :private_specialization?, true

#promote_data_service(full_name, service) ⇒ Object

Method that maps data services from this component's parent models to this composition's own

It is called as needed when calling #each_data_service



16
17
18
# File 'lib/syskit/models/component.rb', line 16

def promote_data_service(full_name, service)
    service.attach(self, verify: false)
end

#promote_dynamic_service(name, dyn) ⇒ Object

Called by the dynamic_service accessors to promote dynamic services from our parent model to the corresponding dynamic services on the child models



449
450
451
# File 'lib/syskit/models/component.rb', line 449

def promote_dynamic_service(name, dyn)
    dyn.attach(self)
end

#provides(model, port_mappings = Hash.new, as: nil, slave_of: nil, bound_service_class: BoundDataService) ⇒ Object

Declares that this component provides the given data service. model can either be the data service constant name (from Syskit::DataServices), or its plain name.

If the data service defines an interface, the component must provide the required input and output ports. If an ambiguity exists, explicit port mappings must be provided.

Examples:

Invalid service overriding. This is an error if Service2

does not provide Service1

class TaskModel < Component
  provides Service, as: 'service'
end
class SubTaskModel < TaskModel
  provides Service2, as: 'service2'
end

Parameters:

  • arguments (Hash)

    option hash, as well as explicit port mappings. The values that are not reserved options (listed below) are used as port mappings, of the form:

    component_port_name => service_port_name
    

    I.e. they specify that service_port_name on the service should be mapped to component_port_name on the component

Raises:

  • ArgumentError if a data service with that name already exists

  • SpecError if the new data service overrides a data service from the parent, but does not provide the service from this parent. See example below.



610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
# File 'lib/syskit/models/component.rb', line 610

def provides(model, port_mappings = Hash.new, as: nil, slave_of: nil, bound_service_class: BoundDataService)
    unless model.kind_of?(DataServiceModel)
        if model.kind_of?(Roby::Models::TaskServiceModel)
            return super(model)
        else
            raise ArgumentError, "expected a data service model as argument and got #{model}"
        end
    end

    unless as
        raise ArgumentError, "no service name given, please use the as: option"
    end

    name = as.to_str
    full_name = name

    if master = slave_of
        if master.respond_to?(:to_str)
            master_srv = find_data_service(master)
            if !master_srv
                raise ArgumentError, "master data service #{master_source} is not registered on #{self}"
            end
            master = master_srv
        end
        full_name = "#{master.full_name}.#{name}"
    end

    # Get the source name and the source model
    if data_services[full_name]
        raise ArgumentError, "there is already a data service named '#{full_name}' defined on '#{short_name}'"
    end

    # If a source with the same name exists, verify that the user is
    # trying to specialize it
    if has_data_service?(full_name)
        parent_type = find_data_service(full_name).model
        if !(model <= parent_type)
            raise ArgumentError, "#{self} has a data service named #{full_name} of type #{parent_type}, which is not a parent type of #{model}"
        end
    end

    if master && (master.component_model != self)
        data_services[master.full_name] = master.attach(self)
    end

    begin
        new_port_mappings = compute_port_mappings(model, port_mappings)
    rescue InvalidPortMapping => e
        raise InvalidProvides.new(self, model, e), "#{short_name} does not provide the '#{model.name}' service's interface. #{e.message}", e.backtrace
    end

    service = bound_service_class.new(name, self, master, model, Hash.new)
    service.port_mappings[model] = new_port_mappings

    # Now, adapt the port mappings from +model+ itself and map
    # them into +service.port_mappings+
    Models.update_port_mappings(service.port_mappings, new_port_mappings, model.port_mappings)

    # Remove from +arguments+ the items that were port mappings
    new_port_mappings.each do |from, to|
        if port_mappings[from].to_s == to # this was a port mapping !
            port_mappings.delete(from)
        elsif port_mappings[from.to_sym].to_s == to
            port_mappings.delete(from.to_sym)
        end
    end
    if !port_mappings.empty?
        raise InvalidProvides.new(self, model), "invalid port mappings: #{port_mappings} do not match any ports in either #{self} or #{service}"
    end

    include model

    data_services[full_name] = service

    Models.debug do
        Models.debug "#{short_name} provides #{model.short_name}"
        Models.debug "port mappings"
        service.port_mappings.each do |m, mappings|
            Models.debug "  #{m.short_name}: #{mappings}"
        end
        break
    end

    return service
end

#provides_dynamic(service_model, port_mappings = Hash.new, as: nil, slave_of: nil, bound_service_class: BoundDataService) ⇒ BoundDynamicDataService

Declares that this component model will dynamically provide the ports necessary to provide a service model

The main difference when compared to #provides is that service ports that are not mapped to the task are automatically created (provided a corresponding dynamic_input_port or dynamic_output_port declaration exists on the oroGen model).

Parameters:

  • service_model (Syskit::DataService)

    the service model

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

    explicit port mappings needed to resolve the service's ports to the task's ports

  • as (String)

    the name of the newly created BoundDynamicDataService

  • slave_of (BoundDataService, String, nil)

    if this service is slave of another, the master service

Returns:



435
436
437
438
439
440
441
442
443
444
# File 'lib/syskit/models/component.rb', line 435

def provides_dynamic(service_model, port_mappings = Hash.new, as: nil, slave_of: nil, bound_service_class: BoundDataService)
    # Do not use #filter_options here, it will transform the
    # port names into symbols
    port_mappings = DynamicDataService.update_component_model_interface(
        self, service_model, port_mappings)
    provides(service_model, port_mappings,
             as: as,
             slave_of: slave_of,
             bound_service_class: bound_service_class)
end

#proxy_task_model(service_models, as: nil, extension: Placeholder) ⇒ Object

Deprecated.

use Models::Placeholder.for instead



930
931
932
933
# File 'lib/syskit/models/component.rb', line 930

def proxy_task_model(service_models, as: nil, extension: Placeholder)
    Roby.warn_deprecated "Component.proxy_task_model is deprecated, use Syskit::Models::Placeholder.for instead"
    extension.for(service_models, component_model: self, as: as)
end

#register_placeholder_model(placeholder_m, service_models, placeholder_type = Placeholder) ⇒ 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.

Register a new placeholder model for the given service models and placeholder type



890
891
892
893
# File 'lib/syskit/models/component.rb', line 890

def register_placeholder_model(placeholder_m, service_models, placeholder_type = Placeholder)
    by_type = (placeholder_models[service_models] ||= Hash.new)
    by_type[placeholder_type] = placeholder_m
end

#require_dynamic_service(dynamic_service_name, as: nil, **dyn_options) ⇒ BoundDynamicDataService

Instanciate a dynamic service on this model

Parameters:

  • dynamic_service_name (String)

    the name under which the dynamic service got registered when calling #dynamic_service

  • as (String)

    the name of the newly created service

  • dyn_options

    options passed to the dynamic service block through DynamicDataService#instanciate

Returns:



536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/syskit/models/component.rb', line 536

def require_dynamic_service(dynamic_service_name, as: nil, **dyn_options)
    if !as
        raise ArgumentError, "no name given, please provide the as: option"
    end
    service_name = as.to_s

    dyn = find_dynamic_service(dynamic_service_name)
    if !dyn
        raise ArgumentError, "#{short_name} has no dynamic service called #{dynamic_service_name}, available dynamic services are: #{each_dynamic_service.map { |name, _| name }.sort.join(", ")}"
    end

    if srv = find_data_service(service_name)
        if srv.fullfills?(dyn.service_model)
            return srv
        else raise ArgumentError, "there is already a service #{service_name}, but it is of type #{srv.model.short_name} while the dynamic service #{dynamic_service_name} expects #{dyn.service_model.short_name}"
        end
    end
    dyn.instanciate(service_name, **dyn_options)
end

#resolve(task) ⇒ Object

Deprecated.

use #bind instead



850
851
852
853
854
# File 'lib/syskit/models/component.rb', line 850

def resolve(task)
    Roby.warn_deprecated "#{__method__} is deprecated, use "\
        "Models::Component#bind instead"
    bind(task)
end

#selected_for(requirements) ⇒ Object



1117
1118
1119
# File 'lib/syskit/models/component.rb', line 1117

def selected_for(requirements)
    InstanceSelection.new(nil, self.to_instance_requirements, requirements.to_instance_requirements)
end

#self_port?(port) ⇒ Boolean

Test if the given port is a port of self

Parameters:

Returns:

  • (Boolean)


731
732
733
# File 'lib/syskit/models/component.rb', line 731

def self_port?(port)
    port.component_model == self
end

#self_port_to_component_port(port) ⇒ Models::Port

Resolves the given port model into a component model where #component_model is a proper component (e.g. not a BoundDataService)

It is not meant to be used directly. Use Port#to_component_port instead

Parameters:

Returns:



311
312
313
# File 'lib/syskit/models/component.rb', line 311

def self_port_to_component_port(port)
    return port
end

#specialization_counterObject

An ID that represents which specialized model this is



743
744
745
746
# File 'lib/syskit/models/component.rb', line 743

def specialization_counter
    @specialization_counter ||= 0
    @specialization_counter += 1
end

#specialize(name = nil) ⇒ Object

Creates a private specialization of the current model



755
756
757
758
759
760
761
762
763
764
765
766
# File 'lib/syskit/models/component.rb', line 755

def specialize(name = nil)
    klass = create_private_specialization
    if name
        klass.name = name
    else
        klass.name = "#{self.name}{#{self.specialization_counter}}"
    end
    klass.private_specialization = true
    klass.private_model
    klass.concrete_model = concrete_model
    klass
end

#stub(&block) ⇒ Object

Define a module that should be applied on the underlying Orocos::RubyTasks::StubTaskContext when running tests in non-live mode



119
120
121
# File 'lib/syskit/models/component.rb', line 119

def stub(&block)
    stub_modules.first.class_eval(&block)
end

#stub_moduleArray<Module>

List of modules that should be applied on the underlying Orocos::RubyTasks::StubTaskContext when running tests in non-stub mode

Returns:

See Also:



34
# File 'lib/syskit/models/component.rb', line 34

inherited_attribute(:stub_module, :stub_modules) { [Module.new] }

#supermodelObject

The model next in the ancestry chain, or nil if self is root



173
174
175
176
177
# File 'lib/syskit/models/component.rb', line 173

def supermodel
    if superclass.respond_to?(:register_submodel)
        return superclass
    end
end

#to_component_modelObject



10
# File 'lib/syskit/models/component.rb', line 10

def to_component_model; self end

#try_bind(object) ⇒ nil, Syskit::Component

Returns task if it fullfills self, nil otherwise

Returns:

See Also:



831
832
833
# File 'lib/syskit/models/component.rb', line 831

def try_bind(object)
    object if object.fullfills?(self)
end

#try_resolve(task) ⇒ Object

Deprecated.

use #try_bind instead



857
858
859
860
861
# File 'lib/syskit/models/component.rb', line 857

def try_resolve(task)
    Roby.warn_deprecated "#{__method__} is deprecated, use "\
        "Models::Component#try_bind instead"
    try_bind(task)
end

#use_conf(*spec, &block) ⇒ Object

Deprecated.


216
217
218
# File 'lib/syskit/models/component.rb', line 216

def use_conf(*spec, &block)
    with_conf(*spec, &block)
end

#use_deployments(*selection) ⇒ Object

Deprecated.


206
207
208
# File 'lib/syskit/models/component.rb', line 206

def use_deployments(*selection)
    prefer_deployed_tasks(*selection)
end

#with_arguments(*spec, &block) ⇒ Object

This returns an InstanciatedComponent object that can be used in other #use statements in the deployment spec

For instance,

add(Cmp::CorridorServoing).
    use(Cmp::Odometry.with_arguments('special_behaviour' => true))


187
188
189
# File 'lib/syskit/models/component.rb', line 187

def with_arguments(*spec, &block)
    InstanceRequirements.new([self]).with_arguments(*spec, &block)
end

#with_conf(*spec, &block) ⇒ Object

This returns an InstanciatedComponent object that can be used in other #use statements in the deployment spec

For instance,

add(Cmp::CorridorServoing).
    use(Cmp::Odometry.use_conf('special_conf'))


228
229
230
# File 'lib/syskit/models/component.rb', line 228

def with_conf(*spec, &block)
    InstanceRequirements.new([self]).with_conf(*spec, &block)
end

#with_dynamic_service(dynamic_service_name, options = Hash.new) ⇒ Object

Returns a model specialized from 'self' that has the required dynamic service



522
523
524
525
526
# File 'lib/syskit/models/component.rb', line 522

def with_dynamic_service(dynamic_service_name, options = Hash.new)
    model = ensure_model_is_specialized
    model.require_dynamic_service(dynamic_service_name, options)
    model
end