module OroGen::Gen::RTT_CPP::TaskContextGeneration

Module that is used to add code generation functionality to Spec::TaskContext

In the generated code, two classes are actually generated:

By default, the Base class derives from the RTT::TaskContext class. This can be changed by using the subclasses method.

For all task context objects (ports, properties, …) there is one attribute, of the right RTT class, added to the generated TaskContext subclass. The attribute name is always the _[object name], so for instance the presence of the following statement

output_port('time', 'double')

will cause a OutputPort<double> attribute named _time to be added to the generated class (more specifically, to the Base subclass).

Attributes

base_header_code[R]

The code snippets that have been declared for the toplevel scope of TaskBase.hpp.

It is an array of [include_before, code_snippet] pairs, where include_before is true if the code should be added before the Task class definition and false if it should be added after.

See add_base_header_code

base_hook_code[R]
{String=>Set<String,#call>}

a set of code snippets that are

inserted in the base task class hooks. It is a mapping from a hook name (such as 'update') to the set of snippets. Snippets can be given as strings or as an object whose call method is going to return the code as a string

base_implementation_code[R]

The code snippets that have been declared for the toplevel scope of TaskBase.cpp.

It is an array of [include_before, code_snippet] pairs, where include_before is true if the code should be added before the Task class definition and false if it should be added after.

See add_base_implementation_code

generation_handlers[R]

The set of generation handlers. See add_generation_handler

user_hook_code[R]
{String=>Set<String,#call>}

a set of code snippets that are

inserted in the task class hooks. It is a mapping from a hook name (such as 'update') to the set of snippets. Snippets can be given as strings or as an object whose call method is going to return the code as a string

Public Class Methods

new() click to toggle source
# File lib/orogen/gen/tasks.rb, line 349
def initialize
    hooks = %w{configure start update error exception fatal stop cleanup}
    @base_hook_code = Hash.new
    hooks.each do |hook_name|
        @base_hook_code[hook_name] = Array.new
    end

    @user_hook_code = Hash.new
    hooks.each do |hook_name|
        @user_hook_code[hook_name] = Array.new
    end

    @generation_handlers = Array.new
    @base_methods = Array.new
    @user_methods = Array.new
    @base_members = Array.new
    @user_members = Array.new
    @base_header_code = Array.new
    @base_implementation_code = Array.new
end
validate_code_object(string, block) click to toggle source

Internal helper that validates string, block as a single code object

The returned object responds to call, where call returns the code string If block is given instead of a string, that block will be called and should return the code as a string

If both are given, an ArgumentError exception is raised.

# File lib/orogen/gen/tasks.rb, line 600
def self.validate_code_object(string, block)
    if string && block
        raise ArgumentError, "you can provide either a string or a block, not both"
    end
    if string
        lambda { string.to_str }
    else
        block
    end
end

Public Instance Methods

add_base_construction(kind, name, code, &block) click to toggle source
# File lib/orogen/gen/tasks.rb, line 956
def add_base_construction(kind, name, code, &block)
    add_base_member(kind, name).constructor(code, &block)
end
add_base_destruction(kind, name, code) click to toggle source
# File lib/orogen/gen/tasks.rb, line 960
def add_base_destruction(kind, name, code)
    add_base_member(kind, name).destructor(code, &block)
end
add_base_header_code(string, include_before = true, &block) click to toggle source

Add some code that needs to be added to the toplevel scope in TaskBase.hpp

# File lib/orogen/gen/tasks.rb, line 645
def add_base_header_code(string, include_before = true, &block)
    code = TaskContextGeneration.validate_code_object(string, block)
    @base_header_code << [include_before, code]
end
add_base_implementation_code(string, include_before = true, &block) click to toggle source

Add some code that needs to be added to the toplevel scope in TaskBase.cpp

# File lib/orogen/gen/tasks.rb, line 652
def add_base_implementation_code(string, include_before = true, &block)
    code = TaskContextGeneration.validate_code_object(string, block)
    @base_implementation_code << [include_before, code]
end
add_base_member(kind, name, type = nil) click to toggle source

Add a code snippet to the generated Base class declaration

# File lib/orogen/gen/tasks.rb, line 936
def add_base_member(kind, name, type = nil)
    if @base_members.any? { |m| m.kind == kind && m.name == name }
        raise ArgumentError, "duplicate name #{kind}:#{name} used for base member"
    end

    m = GeneratedMember.new(self, kind, name, type)
    @base_members << m
    m
end
add_base_method(return_type, name, signature = "") click to toggle source

Define a new method on the Base class of this task

return_type is a string representing the C++ return type for this method, name the method name and signature the arguments as they would be written in C++, without the parenthesis.

For instance

add_base_method("bool", "isCompleted", "int arg")

Generates the method

bool isCompleted(int arg);

Note that you do not have to do this explicitely if add_user_method is called: add_user_method will add a pure virtual method to the base class

It returns an instance of GeneratedMethod that can be used to setup the method further

# File lib/orogen/gen/tasks.rb, line 876
def add_base_method(return_type, name, signature = "")
    m = add_method("base_methods", return_type, name, signature)
    m.in_base = true
    m
end
add_code_to_base_method_after(name,code) click to toggle source

This function adds @param code [String] AFTER the already defined code on the @param name [String] given method

# File lib/orogen/gen/tasks.rb, line 890
def add_code_to_base_method_after(name,code)
    self_base_methods.each do |p|
        if p.name == name
            p.add_to_body_after(code)
            return self 
        end
    end
    raise ArgumentError "Method #{name} could not be found"
end
add_code_to_base_method_before(name,code) click to toggle source

This function adds @param code [String] BEFORE the already defined code on the @param name [String] given method

# File lib/orogen/gen/tasks.rb, line 902
def add_code_to_base_method_before(name,code)
    self_base_methods.each do |p|
        if p.name == name
            p.add_to_body_before(code)
            return self 
        end
    end
    raise ArgumentError "Method #{name} could not be found"
end
add_generation_handler(&block) click to toggle source

Registers a method that should be called at generation time

The provided block will be called at the beginning of the generation process. If the block expects an argument, it will be given the task object

# File lib/orogen/gen/tasks.rb, line 639
def add_generation_handler(&block)
    generation_handlers << block
end
add_method(kind, return_type, name, signature) click to toggle source

Helper method for {#add_base_method} and {#add_user_method}

# File lib/orogen/gen/tasks.rb, line 845
def add_method(kind, return_type, name, signature)
    self_set = send("self_#{kind}")
    if !name.respond_to?(:to_str)
        raise ArgumentError, "expected a string for 'name', got #{name} (#{name.class})"
    elsif self_set.any? { |m| m.name == name }
        raise ArgumentError, "there is already a method called #{name} defined at this level"
    end

    m = GeneratedMethod.new(self, return_type, name, signature)
    self_set << m
    m
end
add_user_member(kind, name, type = nil) click to toggle source

Add a code snippet to the generated user class declaration

# File lib/orogen/gen/tasks.rb, line 947
def add_user_member(kind, name, type = nil)
    if @user_members.any? { |m| m.kind == kind && m.name == name }
        raise ArgumentError, "duplicate name #{kind}:#{name} used for base member"
    end
    m = GeneratedMember.new(kind, name, type)
    @user_members << m
    m
end
add_user_method(return_type, name, signature = "") click to toggle source

Define a new method on the user-part class of this task

It will also add a pure-virtual method with the same signature on the Base class, to ensure that the user does define the method on its side.

It returns an instance of GeneratedMethod that can be used to setup the method further

# File lib/orogen/gen/tasks.rb, line 921
def add_user_method(return_type, name, signature = "")
    if !has_base_method?(name)
        # Add a pure virtual method to remind the user that he
        # should add it to its implementation
        m = add_base_method(return_type, name, signature)
        m.doc "If the compiler issues an error at this point, it is probably that",
                "you forgot to add the corresponding method to the #{self.name} class."
    end
    add_method("user_methods", return_type, name, signature)
end
basename() click to toggle source

Returns the name without an eventual library name

# File lib/orogen/gen/tasks.rb, line 336
def basename
    self.name.split("::").last
end
basepath() click to toggle source

This method generates the relative basepath for generation of all files

# File lib/orogen/gen/tasks.rb, line 312
def basepath
    s = File.join(namespace.split("::").join(File::SEPARATOR))
    if !s.empty?
        s = s + File::SEPARATOR
    end
    s
end
check_uniqueness(name) click to toggle source
Calls superclass method
# File lib/orogen/gen/tasks.rb, line 370
def check_uniqueness(name)
    super

    # Check if that name is a method name in orocos.rb as well ...
    # To warn about name clashes
    if @orocos_rb.nil?
        begin
            require 'orocos'
            @orocos_rb = true
        rescue Exception
            @orocos_rb = false
        end
    end

    if name.to_str != 'state' && @orocos_rb && !project.kind_of?(ImportedProject)
        if Orocos::TaskContext.instance_methods.find { |n| n.to_s == name.to_str }
            STDERR.puts "WARN: #{name} is a method name used in orocos.rb"
            STDERR.puts "WARN:   if you keep that name, you will not be able to use shortcut access in orocos.rb"
            STDERR.puts "WARN:   for instance, for a property, you will have to do"
            STDERR.puts "WARN:      value = my_task.property('#{name}').read(new_value)"
            STDERR.puts "WARN:   instead of the shorter and clearer"
            STDERR.puts "WARN:      value = my_task.#{name}"
        end
    end
end
class_name() click to toggle source
# File lib/orogen/gen/tasks.rb, line 345
def class_name
    name
end
create_dynamic_updater(name, superclass_has_dynamic) click to toggle source
# File lib/orogen/gen/tasks.rb, line 473
def create_dynamic_updater(name, superclass_has_dynamic)
    add_base_method("bool",name)

    if superclass_has_dynamic
        #Call the superclass method if needed, returning false if it fail. Otherwise check our dynamic properties
        #they are generated in register_for_generation, or returning true in the end
        add_code_to_base_method_after name,"        return #{superclass.name}::#{name}();\n"
    else                
        #No superclass code, so return simply true
        add_code_to_base_method_after name,"        return true;\n"
    end
end
full_namespace() click to toggle source
# File lib/orogen/gen/tasks.rb, line 327
def full_namespace
    self.name.split("::")[0..-2].join("::")
end
generate() click to toggle source

Generate the code files for this task. This builds to classes:

  • a #{task.name}Base class in .orogen/tasks/#{task.name}Base.{cpp,hpp} which is the automatically generated part of the task.

  • a #{task.name} class in tasks/#{task.name}.{cpp,hpp} which is the user-provided part of the task. This class is a public subclass of the Base class.

# File lib/orogen/gen/tasks.rb, line 494
def generate
    return if external_definition?
    

    if superclass.name == "RTT::TaskContext"
        hidden_operation("getModelName").
            returns("std::string").
            doc("returns the oroGen model name for this task").
            base_body("    return \"#{name}\";").
            runs_in_caller_thread

        add_base_implementation_code("#ifdef HAS_GETTID\n#include <sys/syscall.h>\n#endif")
        hidden_operation("__orogen_getTID").
            returns("int").
            base_body("    #ifdef HAS_GETTID\nreturn syscall(SYS_gettid);\n#else\nreturn 0;\n#endif").
            doc("returns the PID for this task")
    else
        add_base_method("std::string", "getModelName","").
            body("    return \"#{name}\";")
    end


    if(has_dynamic_properties?)
        create_dynamic_updater("updateDynamicProperties",superclass.has_dynamic_properties?)
    end

    if(has_dynamic_attributes?)
        create_dynamic_updater("updateDynamicAttributes",superclass.has_dynamic_attributes?)
    end


    extensions.each do |ext|
        if ext.respond_to?(:early_register_for_generation)
            OroGen.warn "The plugin #{ext.name} defines a \"early_register_for_generation\" hook, this got renamed to \"pre_generation_hook\" please adapt the code or contact the developer."
            ext.early_register_for_generation(self)
        end
        if ext.respond_to?(:pre_generation_hook)
            ext.pre_generation_hook(self)
        end
    end

    self_properties.each(&:register_for_generation) #needs to be called before operations, because it adds code to them
    self_attributes.each(&:register_for_generation)
    new_operations.each(&:register_for_generation)
    self_ports.each(&:register_for_generation)
    extensions.each do |ext|
        if ext.respond_to?(:generation_hook)
            ext.generation_hook(self)
        end
        if ext.respond_to?(:register_for_generation)
            OroGen.warn "The plugin #{ext.name} defines a \"register_for_generation\" hook, this got renamed to \"generation_hook\" please adapt the code or contact the developer."
            ext.register_for_generation(self)
        end
    end
   
    generation_handlers.each do |h|
        if h.arity == 1
            h.call(self)
        else
            h.call
        end
    end

    # Make this task be available in templates as 'task'
    task = self

    extensions.each do |ext|
        if ext.respond_to?(:post_generation_hook)
            ext.post_generation_hook(self)
        end
    end

    base_code_cpp = Generation.render_template 'tasks', 'TaskBase.cpp', binding
    base_code_hpp = Generation.render_template 'tasks', 'TaskBase.hpp', binding
    Generation.save_automatic "tasks",basepath, "#{basename}Base.cpp", base_code_cpp
    Generation.save_automatic "tasks",basepath, "#{basename}Base.hpp", base_code_hpp

    code_cpp = Generation.render_template "tasks", "Task.cpp", binding
    code_hpp = Generation.render_template "tasks", "Task.hpp", binding
    file_cpp = Generation.save_user "tasks",basepath, "#{basename}.cpp", code_cpp
    file_hpp = Generation.save_user "tasks",basepath, "#{basename}.hpp", code_hpp

    # Validate constructors of old task files
    validate_constructors(file_cpp, basename)
    validate_constructors(file_hpp, basename)

    fake_install_dir = File.join(project.base_dir, AUTOMATIC_AREA_NAME, project.name)
    FileUtils.mkdir_p fake_install_dir
    FileUtils.mkdir_p File.join(fake_install_dir, basepath)

    FileUtils.ln_sf File.join(project.base_dir, "tasks",basepath, "#{basename}.hpp"),
        File.join(fake_install_dir, basepath, "#{basename}.hpp")
    FileUtils.ln_sf File.join(project.base_dir, AUTOMATIC_AREA_NAME, "tasks",basepath, "#{basename}Base.hpp"),
        File.join(fake_install_dir, basepath ,"#{basename}Base.hpp")

    self
end
has_base_method?(name) click to toggle source

Returns true if name is a method defined with add_base_method or add_user_method

# File lib/orogen/gen/tasks.rb, line 884
def has_base_method?(name)
    all_base_methods.any? { |m| m.name == name }
end
header_file() click to toggle source

The name of the header file containing the C++ code which defines this task context

# File lib/orogen/gen/tasks.rb, line 322
def header_file
    project_name = self.name.split("::").first
    File.join(project_name.downcase, basepath, "#{basename}.hpp")
end
in_base_hook(hook, string = nil, &block) click to toggle source

Call to add some code to the generated hooks in the Base task classes

# File lib/orogen/gen/tasks.rb, line 668
def in_base_hook(hook, string = nil, &block)
    in_hook(@base_hook_code, hook, string, &block)
end
in_user_hook(hook, string = nil, &block) click to toggle source

Call to add some code to the generated hooks in the Base task classes

# File lib/orogen/gen/tasks.rb, line 674
def in_user_hook(hook, string = nil, &block)
    in_hook(@user_hook_code, hook, string, &block)
end
interface_types() click to toggle source

Returns the set of types that are used to define this task context, as an array of subclasses of Typelib::Type.

# File lib/orogen/gen/tasks.rb, line 426
def interface_types
    each_interface_type.to_a
end
linux?() click to toggle source

True if we are generating for Linux

# File lib/orogen/gen/tasks.rb, line 341
def linux?;     project.linux? end
namespace() click to toggle source
# File lib/orogen/gen/tasks.rb, line 331
def namespace
    self.name.split("::")[1..-2].join("::")
end
self_base_members(&block) click to toggle source
# File lib/orogen/gen/tasks.rb, line 932
def self_base_members(&block); @base_members end
self_user_members(&block) click to toggle source
# File lib/orogen/gen/tasks.rb, line 933
def self_user_members(&block); @user_members end
used_task_libraries() click to toggle source

The set of task libraries that are required by this task context

This is the set of task libraries that implement our superclasses

# File lib/orogen/gen/tasks.rb, line 416
def used_task_libraries
    project.used_task_libraries.find_all do |tasklib|
        tasklib.self_tasks.any? do |task|
            implements?(task.name)
        end
    end
end
used_typekits() click to toggle source

Returns the set of typekits that define the types used in this task's interface. They are required at compile and link time because of the explicit instanciation of interface templates (ports, …)

# File lib/orogen/gen/tasks.rb, line 434
def used_typekits
    types = interface_types
    project.used_typekits.find_all do |tk|
        types.any? do |type|
            tk.includes?(type.name)
        end
    end.to_set
end
validate_constructors(filename, taskname) click to toggle source

Validate the constructors of the task files regarding an fixed initial state This might be the case when needs_configuration has been specified on a later stage, but still the constructors need to be changed

# File lib/orogen/gen/tasks.rb, line 448
def validate_constructors(filename, taskname)
    if ! fixed_initial_state?
        return
    end

    File.open(filename) do |file|
        begin
            while true
                line = file.readline
                begin
                    if Regexp.new(taskname + "\(.*\)").match(line)
                        if $1 =~ /TaskCore::TaskState/
                            puts  "\nWarning: 'needs_configuration' has been specified for the task '#{taskname}', but the task's constructor has not been updated after this change.\n\n Note: setting a TaskState is not allowed in combination with using 'needs_configuration'.\n Constructors in #{filename} and corresponding files require adaption."
                        end
                    end
                rescue ArgumentError => e 
                    STDERR.puts "[CRITICAL] Could not parse \'#{line}\' maybe it contains invalid chars?"
                    raise e
                end
            end
        rescue EOFError
        end
    end
end
xenomai?() click to toggle source

True if we are generating for Xenomai

# File lib/orogen/gen/tasks.rb, line 343
def xenomai?;   project.xenomai? end