Module: OroGen::Gen::RTT_CPP::TaskContextGeneration

Defined in:
lib/orogen/gen/tasks.rb

Overview

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

In the generated code, two classes are actually generated:

  • the auto-generated code is in .orogen/tasks/[name]Base.cpp and .orogen/tasks/[name]Base.hpp. These files define the various attributes related to the task context (i.e. port and attribute objects) in an [project.name]::[name]Base class.

  • the user-visible code is in tasks/[name].cpp and tasks/[name].hpp. These files are the ones that define the [project.name]::[name] class, which is a direct subclass of the [project.name]::[name]Base class.

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).

Defined Under Namespace

Classes: GeneratedMember, GeneratedMethod, GeneratedObject

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#base_header_codeObject (readonly)

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


619
620
621
# File 'lib/orogen/gen/tasks.rb', line 619

def base_header_code
  @base_header_code
end

#base_hook_codeObject (readonly)

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


683
684
685
# File 'lib/orogen/gen/tasks.rb', line 683

def base_hook_code
  @base_hook_code
end

#base_implementation_codeObject (readonly)

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


629
630
631
# File 'lib/orogen/gen/tasks.rb', line 629

def base_implementation_code
  @base_implementation_code
end

#generation_handlersObject (readonly)

The set of generation handlers. See #add_generation_handler


632
633
634
# File 'lib/orogen/gen/tasks.rb', line 632

def generation_handlers
  @generation_handlers
end

#user_hook_codeObject (readonly)

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


690
691
692
# File 'lib/orogen/gen/tasks.rb', line 690

def user_hook_code
  @user_hook_code
end

Class Method Details

.validate_code_object(string, block) ⇒ Object

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.


600
601
602
603
604
605
606
607
608
609
# 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

Instance Method Details

#add_base_construction(kind, name, code, &block) ⇒ Object


956
957
958
# 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) ⇒ Object


960
961
962
# 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) ⇒ Object

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


645
646
647
648
# 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) ⇒ Object

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


652
653
654
655
# 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) ⇒ Object

Add a code snippet to the generated Base class declaration


936
937
938
939
940
941
942
943
944
# 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 = "") ⇒ Object

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


876
877
878
879
880
# 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) ⇒ Object

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


890
891
892
893
894
895
896
897
898
# 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) ⇒ Object

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


902
903
904
905
906
907
908
909
910
# 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) ⇒ Object

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


639
640
641
# File 'lib/orogen/gen/tasks.rb', line 639

def add_generation_handler(&block)
    generation_handlers << block
end

#add_method(kind, return_type, name, signature) ⇒ Object

Helper method for #add_base_method and #add_user_method


845
846
847
848
849
850
851
852
853
854
855
856
# 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) ⇒ Object

Add a code snippet to the generated user class declaration


947
948
949
950
951
952
953
954
# 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 = "") ⇒ Object

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


921
922
923
924
925
926
927
928
929
930
# 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

#basenameObject

Returns the name without an eventual library name


336
337
338
# File 'lib/orogen/gen/tasks.rb', line 336

def basename
    self.name.split("::").last
end

#basepathObject

This method generates the relative basepath for generation of all files


312
313
314
315
316
317
318
# 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) ⇒ Object


370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# 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_nameObject


345
346
347
# File 'lib/orogen/gen/tasks.rb', line 345

def class_name
    name
end

#create_dynamic_updater(name, superclass_has_dynamic) ⇒ Object


473
474
475
476
477
478
479
480
481
482
483
484
# 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_namespaceObject


327
328
329
# File 'lib/orogen/gen/tasks.rb', line 327

def full_namespace
    self.name.split("::")[0..-2].join("::")
end

#generateObject

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

  • a #OroGen::Gen::RTT_CPP::TaskContextGeneration.tasktask.nameBase class in .orogen/tasks/#OroGen::Gen::RTT_CPP::TaskContextGeneration.tasktask.nameBase.cpp,hpp which is the automatically generated part of the task.

  • a #OroGen::Gen::RTT_CPP::TaskContextGeneration.tasktask.name class in tasks/#OroGen::Gen::RTT_CPP::TaskContextGeneration.tasktask.name.cpp,hpp which is the user-provided part of the task. This class is a public subclass of the Base class.


494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# 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) ⇒ Boolean

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


884
885
886
# File 'lib/orogen/gen/tasks.rb', line 884

def has_base_method?(name)
    all_base_methods.any? { |m| m.name == name }
end

#header_fileObject

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


322
323
324
325
# 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) ⇒ Object

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


668
669
670
# 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_hook(set, hook, string, &block) ⇒ Object

Helper method for in_base_hook and in_user_hook


658
659
660
661
662
663
664
# File 'lib/orogen/gen/tasks.rb', line 658

def in_hook(set, hook, string, &block) # :nodoc:
    code = TaskContextGeneration.validate_code_object(string, block)
    if !set.has_key?(hook)
        raise ArgumentError, "unknown hook '#{hook}', must be one of #{@additional_base_hook_code.keys.join(", ")}"
    end
    set[hook] << (string || block)
end

#in_user_hook(hook, string = nil, &block) ⇒ Object

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


674
675
676
# 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

#initializeObject


349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# 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

#interface_typesObject

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


426
427
428
# File 'lib/orogen/gen/tasks.rb', line 426

def interface_types
    each_interface_type.to_a
end

#linux?Boolean

True if we are generating for Linux


341
# File 'lib/orogen/gen/tasks.rb', line 341

def linux?;     project.linux? end

#namespaceObject


331
332
333
# File 'lib/orogen/gen/tasks.rb', line 331

def namespace
    self.name.split("::")[1..-2].join("::")
end

#self_base_members(&block) ⇒ Object


932
# File 'lib/orogen/gen/tasks.rb', line 932

def self_base_members(&block); @base_members end

#self_user_members(&block) ⇒ Object


933
# File 'lib/orogen/gen/tasks.rb', line 933

def self_user_members(&block); @user_members end

#state_global_value_name(state_name, state_type) ⇒ Object

Returns the C++ value name for the given state when defined globally


403
404
405
# File 'lib/orogen/gen/tasks.rb', line 403

def state_global_value_name(state_name, state_type) # :nodoc:
    "#{basename}_#{state_name.upcase}"
end

#state_local_value_name(state_name, state_type) ⇒ Object

Returns the C++ value name for the given state when defined in the associated class scope.


409
410
411
# File 'lib/orogen/gen/tasks.rb', line 409

def state_local_value_name(state_name, state_type) # :nodoc:
    state_name.upcase
end

#state_type_nameObject

Returns the type name for the state enumeration


397
398
399
# File 'lib/orogen/gen/tasks.rb', line 397

def state_type_name # :nodoc:
    "#{basename}_STATES"
end

#used_task_librariesObject

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

This is the set of task libraries that implement our superclasses


416
417
418
419
420
421
422
# 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_typekitsObject

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, …)


434
435
436
437
438
439
440
441
# 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) ⇒ Object

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


448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# 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?Boolean

True if we are generating for Xenomai


343
# File 'lib/orogen/gen/tasks.rb', line 343

def xenomai?;   project.xenomai? end