Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

importing peers in functions #149

Open
ahoarau opened this issue Apr 26, 2016 · 5 comments
Open

importing peers in functions #149

ahoarau opened this issue Apr 26, 2016 · 5 comments

Comments

@ahoarau
Copy link
Contributor

ahoarau commented Apr 26, 2016

The following code causes an error :

global void import_stuff()
{
   import("rtt_ros") # creates "this.ros"
   ros.import("test")
}

deployer -s import_test.ops :

import_test.ops :Parse error at line 5: Service or Task "Deployer" has no Peer or Service ros (or Deployer was not found at all).

But this does not :

global void import_stuff()
{
   import("rtt_ros")
   scripting.eval("ros.import(\"test\")")
}

Whenever a function ran into runScript, it does not accept newly created services/peers.

Why is it necessary to check for existing peers/services inside functions ?

@ahoarau
Copy link
Contributor Author

ahoarau commented Dec 5, 2017

@meyerj gentle ping on this issue.

@meyerj
Copy link
Member

meyerj commented Dec 5, 2017

There is not much you can do about that without refactoring the whole scripting engine. Orocos does not evaluate every line of a function or program on the fly. In a first step it parses the function and already creates all the necessary data sources, operation callers etc., in order to be able to execute it with real-time constraints (under some additional assumptions and depending on the data types involved). At the time the parser sees the line ros.import("test") it has to know that ros resolves to a service registered globally and that this service provides an operation named import with a certain signature, but the function has not been executed yet and hence the ros service does not exist.

It might be possible to automatically delay the evaluation of a line until the function is actually executed in case of an error during the initial parsing step, so basically to interpret the first definition of import_stuff() like in the second in your example. But that does not appear to be a sensible approach to me if real-time execution really matters and might hide potential problems it the function is really called repeatedly in a real-time context.

Your use case looks like import_stuff() only needs to be called once during deployment, but ROS is optional in your application and you do not want to import package rtt_ros unconditionally, outside the function? In that case you could move the body of the function into a separate file import_stuff.ops and "call" it with scripting.runScript("import_stuff.ops") instead?

@ahoarau
Copy link
Contributor Author

ahoarau commented Dec 7, 2017

I'm trying to get to a sort-of-dynamic deployment, where you load a big orocos lib that allows users to create components in one line (instead of the whole loadComponent, setActivity, configure, start, connect to the provided component etc). Something like :

import("rtt_rospack")
ros.import("components_lib.ops") // <-- Defines loadComp1(), loadComp2() etc
loadComp1()

And loadComp1() is defined like :

global void loadComp1()
{
   loadComponent("hello","OCL::HelloWorld")
   hello.do_stuff()
}

This code returns : Parse error at line 6: Service or Task "Deployer" has no Peer or Service hello

Orocos does not evaluate every line of a function or program on the fly.

Now I get why I have this behavior and I think i'm using the scripting service the wrong way.
It's true that It could be better to have :

scripting.runScript("import_comp1.ops")
scripting.runScript("import_comp2.ops")

But I'd like to have arguments to the functions, and that is difficult to achieve without ros.

Maybe something like :

scripting.runScriptWithArgs("import_comp.ops", strings("is_sim","my_robot","/link_0"))

@meyerj
Copy link
Member

meyerj commented Jan 3, 2018

There are some possibilities how to fix this, but the by far simplest solution is to use scripting.eval("...") inside your generic loadComp() function where necessary, for example:

export void myLoadComponent(string package, string type, string name)
{
  import("rtt_ros")
  scripting.eval("ros.import(\"" + package + "\")")
  loadComponent(name, type)

  // configure component...
}

That approach works for me.

Use TaskContext and Service argument and return types for the DeploymentComponent

Additionally, to avoid lots of eval statements in myLoadComponent(), loadComponent() could actually return a TaskContext * which is already a valid type in the Orocos type system. Instead of the approach you suggested in #249, all deployer operations that operate on TaskContexts should expect TaskContext (or Service) pointer arguments and the parsers could directly operate on pointers instead of strings (with some implicit conversion from string literals to TaskContext * for backwards compatibility). That would be much cleaner and additionally provide some type safety compared to plain strings. Some operations of the DeploymentComponent could be deprecated because there are already equivalent operations in the TaskContext interface.

Example:

export void myLoadComponent(string package, string type, string name)
{
  import("rtt_ros")
  scripting.eval("ros.import(\"" + package + "\")")
  var TaskContext tc = loadComponent(name, type)
  // or
  //   var TaskContext tc = "name" (implicit conversion from string)
  setActivity(tc, 1.0, 0, ORO_SCHED_OTHER)
  tc.loadService("foo")
  var TaskContext tc_peer = tc.peer
  // ...
}

eval keyword

Introduce a new eval keyword to Orocos scripting, similar to the bash built-in eval, in order to delay the parsing of a statement or function body until actual execution, e.g.

import("rtt_rospack")
eval ros.import("test")   // equivalent to scripting.eval("ros.import(\"test\")"

or

export eval void myLoadComponent(string package, string type, string name)
{
   import("rtt_ros")
   ros.import(package)
   loadComponent(name, type)
}
// equivalent to
//   export void myLoadComponent(string package, string type, string name)
//   {
//      scripting.eval("import(\"rtt_ros\")\n" +
//                    "ros.import(\"" + package + "\")\n" +
//                    "loadComponent(\"" + name + "\", \"" + type + "\")
//   }

The first variant might be trivial and just adds some syntactic sugar, while the latter is only a vague idea. Both use cases probably require some effort to properly bind argument values independent of their type. At the moment statements evaluated with scripting.eval() or scripts called with scripting.runScript() cannot access variables in the scope of their caller.

Script arguments

Arguments (and maybe a return value) for scripts would be possible, too, but not trivial to implement either. How would you expect to access the arguments in a script? As argc/argv like in C? Or should arguments always have explicit names assigned?

@ahoarau
Copy link
Contributor Author

ahoarau commented Jan 4, 2018

Thanks for the detailed answer. I think having loadComponent returning a TaskContext is a great alternative. The eval keyword would also be a nice plus, not having to use scripting.eval, and used like eval ros.import("test").

Regarding the script arguments, something like scripting.runScriptWithArgs("file.ops","myrobot true false") could be sufficient to be parsed with argc/argv. It'll probably add way too much overhead to have proper arguments with names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants