-
The main and child scripts
The main script and the child scripts, which are simulation scripts, play the central role in each simulation: while the main script contains the simulation loop code, child scripts contain the typical code to control models (e.g. robots, sensors or actuators).
By default, each scene has a main script that handles all the functionality. Without main script, a simulation cannot run. The main script can be customized, but it is preferable to do all the customization work in a child script.Each scene object can be associated with a child script that will handle a specific part of a simulation, in an independent and distributed fashion. The most common use for child scripts is to have them control a model.
Following are the main differences between the main script and a child script:
- There can only be one main script. There can be an unlimited number of child scripts.
- The main script is independent and should preferably not be customized. Child scripts are associated with scene objects and should be customized.
- The main script is never duplicated in a copy/paste operation of scene objects. Child scripts will duplicate themselves together with their associated scene object.
- The main script cannot be threaded. Child scripts can be threaded or non-threaded.
-
Simulation
VREP中的仿真可以通过工具栏上的开始、暂停、停止按钮进行控制:
[Simulation start/pause/stop toolbar buttons]
在程序内部,仿真过程分为多种中间状态(Internally, the simulator will use additional intermediate states in order to correctly inform scripts or programs about what will happen next),下图反映了这些有限状态间的转变:
[Simulation state diagram]
函数simGetSimulationState()可以返回当前仿真状态,返回值有下面几种:
仿真进程按照固定的时间步长循环执行。The simulator operates by advancing the simulation time at constant time steps. Following figure illustrates the main simulation loop:
[Main simulation loop]
需要注意的是VREP中的仿真时间与真实时间并不一致。If the real-time mode is not enabled, then the V-REP simulation time will not move at the same rate as the time on your watch. Simulation will try to run as fast as possible. This could be faster or slower than real-time, depending on the complexity of the simulation. Real-time simulation is supported by trying to keep the simulation time synchronized with the real time:
[Real-time simulation loop]
By default, each simulation cycle is composed by following sequential operations:
- Executing the main script
- Rendering the scene
仿真速度主要取决于仿真时间步长和渲染一步需要的仿真次数(the number of simulation passes for one rendering pass),在仿真过程中可以通过工具栏上的仿真速度调节按钮控制仿真速度:
[Simulation speed adjustment toolbar buttons]
仿真时间步长和渲染一帧的仿真次数ppf可以在仿真对话框中进行设置,仿真设置按钮位于界面左侧的工具栏上:
[Simulation toolbar button]
选择custom,否则不能进行自定义:
[Simulation settings dialog]
- Time step: the simulation time step. Each time the main script was executed, the simulation time is incremented by the simulation time step. Using large time steps results in fast but inaccurate/unstable simulations. Small time steps on the other hand will (generally) lead to more precise simulations, but will take more time. It is highly recommended to keep a default time step.
- Simulation passes per frame (ppf): the number of simulation passes for one rendering pass. A value of 10 would mean that the main script is executed 10 times (10 simulation steps) before the screen is refreshed. ppf值越大,仿真速度将越快(因为图形渲染要耗费很多时间)。
由于渲染操作很耗费时间,会导致仿真周期变长,减慢仿真速度。我们可以通过增大ppf的值来提高仿真速度,但有时仍不能满足实时性的要求。The rendering operation will always increase the simulation cycle duration, thus also slowing down simulation speed. The number of main script executions per scene rendering can be defined, but this is not enough in some situations, because rendering will still slow down every xth simulation cycle (which can be handicapping with real-time requirements). For those situations, a threaded rendering mode can be activated via the user settings, or via the following toolbar button:
[Threaded rendering toolbar button]
When the threaded rendering mode is activated, a simulation cycle will only consist in execution of the main script, thus simulations will run at maximum speed. Rendering will happen via a different thread, and not slow down the simulation task. 即打开多线程渲染时,图形的渲染工作将会在新的线程中进行,原先的simulation cycle将只执行main script,因此计算速度会大大加快。但这也会带来许多问题,比如渲染和仿真的不同步可能会导致视觉差错的产生。
-
The main script
VREP中的每一个场景都默认带一个主脚本(main script)文件,它包含了基本的控制代码,用于控制仿真进程(Without main script, a running simulation won't do anything)。
[Main script icon]
main script中的代码按照功能可分为初始化部分、常规部分和结束清理部分:
- the initialization part: this part will be executed one time just at the beginning of a simulation. The code is in charge of preparing a simulation, etc.
- the regular part: this part will be executed at each simulation pass. The code is in charge of handling all the functionality of the simulator (inverse kinematics, proximity sensors, collision detection, dynamics, etc.) in a generic way. Two commands are of particular interest: simLaunchThreadedChildScripts and simHandleChildScripts. simLaunchThreadedChildScripts launches threaded child scripts, while simHandleChildScripts runs non-threaded child scripts. Without those commands, child scripts won't be executed, and specific model functionality or behavior won't operate. The regular part is divided into an actuation (or action/reaction) section and a sensing (or probing) section.
- the restoration part: this part will be executed one time just before a simulation ends. The code is in charge of restoring object's initial configuration, clearing sensor states, collision states, etc.
下面是一个典型的main script:
if (sim_call_type==sim_mainscriptcall_initialization) then -- Initialization part: simHandleSimulationStart() simOpenModule(sim_handle_all) simHandleGraph(sim_handle_all_except_explicit,0)endif (sim_call_type==sim_mainscriptcall_regular) then -- Actuation part: simResumeThreads(sim_scriptthreadresume_default) simResumeThreads(sim_scriptthreadresume_actuation_first) simLaunchThreadedChildScripts() simHandleChildScripts(sim_childscriptcall_actuation) simResumeThreads(sim_scriptthreadresume_actuation_last) simHandleCustomizationScripts(sim_customizationscriptcall_simulationactuation) simHandleModule(sim_handle_all,false) simResumeThreads(2) simHandleMechanism(sim_handle_all_except_explicit) simHandleIkGroup(sim_handle_all_except_explicit) simHandleDynamics(simGetSimulationTimeStep()) simHandleMill(sim_handle_all_except_explicit) -- Sensing part: simHandleSensingStart() simHandleCollision(sim_handle_all_except_explicit) simHandleDistance(sim_handle_all_except_explicit) simHandleProximitySensor(sim_handle_all_except_explicit) simHandleVisionSensor(sim_handle_all_except_explicit) simResumeThreads(sim_scriptthreadresume_sensing_first) simHandleChildScripts(sim_childscriptcall_sensing) simResumeThreads(sim_scriptthreadresume_sensing_last) simHandleCustomizationScripts(sim_customizationscriptcall_simulationsensing) simHandleModule(sim_handle_all,true) simResumeThreads(sim_scriptthreadresume_allnotyetresumed) simHandleGraph(sim_handle_all_except_explicit,simGetSimulationTime()+simGetSimulationTimeStep())endif (sim_call_type==sim_mainscriptcall_cleanup) then -- Clean-up part: simResetMilling(sim_handle_all) simResetMill(sim_handle_all_except_explicit) simResetCollision(sim_handle_all_except_explicit) simResetDistance(sim_handle_all_except_explicit) simResetProximitySensor(sim_handle_all_except_explicit) simResetVisionSensor(sim_handle_all_except_explicit) simCloseModule(sim_handle_all)end
-
Child scripts
子脚本是一种仿真脚本(Simulation scripts are embedded scripts that are only executed while a simulation is running),VREP支持无限数量的子脚本。每个子脚本文件包含一小段Lua控制代码并附在场景中的物体上,用于实现特定功能(主要是用于控制场景中的模型):
[Child scripts associated with NAO]
Child scripts可以分为两类: non-threaded child scripts 和 threaded child scripts:
[non-threaded child script icon (left), threaded child script icon (right)]
Non-threaded child scripts are pass-through scripts. This means that every time they are called, they should perform some task and then return control. If control is not returned, then the whole simulation halts. Non-threaded child scripts operate as functions, and are called by the main script twice per simulation step: from the main script's actuation phase, and from the main script's sensing phase. This type of child script should always be chosen over threaded child scripts whenever possible.
如果场景中有多个Non-threaded子脚本,则它们会按照父子关系链的顺序逐级向下执行,即会先从模型的根节点(或没有父节点的物体)开始,逐级向下,直到叶节点(或没有子节点的物体)结束。 Child scripts are executed in a cascaded way: child scripts are executed starting with root objects (or parentless objects), and ending with leaf objects (or childless objects). 可以简单地验证一下,在场景中添加几个物体并创建父子关系链,如下图所示:
在每一个物体的子脚本中加入信息输出代码:simAddStatusbarMessage(string),参数分别为'0'、'1'、'2'、'3',则仿真时会在状态栏中看到输出如下:
non-threaded脚本代码按功能可分为以下4部分:
- the initialization part: this part will be executed just one time (the first time the child script is called). This can be at the beginning of a simulation, but also in the middle of a simulation. Usually you would put some initialization code as well as handle retrieval in this part. 这一部分通常会包含一些初始化代码和句柄获取代码。
- the actuation part: this part will be executed in each simulation step, during the actuation phase of a simulation step.
- the sensing part: this part will be executed in each simulation step, during the sensing phase of a simulation step.
- the restoration part: this part will be executed one time just before a simulation ends, or before the script is destroyed.
下面是一个自动旋转门的non-threaded控制代码,旋转门的前后装有接近传感器,当检测到有人靠近时将门自动打开:
if (sim_call_type==sim_childscriptcall_initialization) then sensorHandleFront=simGetObjectHandle("DoorSensorFront") sensorHandleBack=simGetObjectHandle("DoorSensorBack") motorHandle=simGetObjectHandle("DoorMotor")endif (sim_call_type==sim_childscriptcall_actuation) then resF=simReadProximitySensor(sensorHandleFront) resB=simReadProximitySensor(sensorHandleBack) if ((resF>0)or(resB>0)) then simSetJointTargetVelocity(motorHandle, 0.2) else simSetJointTargetVelocity(motorHandle, 0) endendif (sim_call_type==sim_childscriptcall_sensing) thenendif (sim_call_type==sim_childscriptcall_cleanup) then -- Put some restoration code hereend
Threaded子脚本会在一个新的线程中运行,不必像Non-threaded脚本那样执行后返回。The launch of threaded child scripts is handled by the default main script code, via the simLaunchThreadedChildScripts function, in a cascaded way. When a threaded child script's execution is still underway, it will not be launched a second time. When a threaded child script ended, it can be relaunched only if the execute once item in the script properties is unchecked. 脚本属性对话框可以通过左侧工具栏上的Scripts按钮打开:
[Script toolbar button]
[Script dialog]
- Disabled: indicates whether the script is enabled or disabled.
- Execute just once: this item applies only to threaded child scripts. When this item is unchecked, then a thread that ended will be relaunched by the main script.
- Associated object: object that is currently associated with the script.
- Execution order: specifies the execution order for a scripts. The execution order only has an effect on scripts located on similar hierarchy level. 即处于同一层级的几个脚本可以在这里设置执行顺序。还是用之前的模型进行一下验证,现在将Object2~Object4移到同一层级下,将Object2的执行顺序设为last,Object3的执行顺序设为normal,Object4的执行顺序设为first:
开始仿真,状态栏输出结果如下:
Threaded child scripts have several weaknesses compared to non-threaded child scripts if not programmed appropriately: they are more resource-intensive, they can waste some processing time, and they can be a little bit less responsive to a simulation stop command.
V-REP uses threads to mimic the behavior of coroutines, instead of using them traditionally, which allows for a great deal of flexibility and control: by default a threaded child script will execute for about 1-2 milliseconds before automatically switching to another thread. Once the current thread was switched, it will resume execution at next simulation pass. The thread switching is automatic (occurs after the specified time), but the simSwitchThread command allows to shorten that time when needed.
Following code shows child script synchronization with the main simulation loop:
-- Put your main loop here:threadFunction=function() while simGetSimulationState()~=sim_simulation_advancing_abouttostop do resF=simReadProximitySensor(sensorHandleFront) resB=simReadProximitySensor(sensorHandleBack) if ((resF>0)or(resB>0)) then simSetJointTargetVelocity(motorHandle, 0.2) else simSetJointTargetVelocity(motorHandle, 0) end simSwitchThread() -- Switch to another thread now!(resume in next simulation step) -- from now on, above loop is executed once every time the main script is about to execute. -- this way you do not waste precious computation time and run synchronously. endend-- Put some initialization code here:simSetThreadAutomaticSwitch(false)sensorHandleFront=simGetObjectHandle("DoorSensorFront")sensorHandleBack=simGetObjectHandle("DoorSensorBack")motorHandle=simGetObjectHandle("DoorMotor")-- Here we execute the regular thread code:res,err = pcall(threadFunction) -- pcall to trap errors in a lua function callif not res then simAddStatusbarMessage('Lua runtime error: '..err)end-- Put some clean-up code here:-- ADDITIONAL DETAILS:-- --------------------------------------------------------------------------- If you wish to synchronize a threaded loop with each simulation pass,-- enable the explicit thread switching with ---- simSetThreadAutomaticSwitch(false)---- then use---- simSwitchThread()---- When you want to resume execution in next simulation step (i.e. at t=t+dt)---- simSwitchThread() can also be used normally, in order to not waste too much-- computation time in a given simulation step-- -------------------------------------------------------------------------
Above while loop will now execute exactly once for each main simulation loop and not waste time reading sensors states again and again for same simulation times. By default, threads always resume when the main script calls simResumeThreads.
假如上面的代码中没有调用simSwitchThread()与main script进行同步,则线程中的while循环会以最大速度一直执行。即如果main script以默认速度50ms执行一次,而threaded child script中的while循环可能不到1ms就执行了一次,这样还没等场景中其它物体发生改变(比如人还没有走近),就已经查询传感器状态和设置关节速度很多次,导致资源浪费。
下面可以进行一个简单的测试:新建一个场景,在某个物体下添加一个threaded child script,并加入下面代码向状态栏输出信息:
-- Put some initialization code heresimSetThreadAutomaticSwitch(false)index = 0-- Put your main loop here:while simGetSimulationState()~=sim_simulation_advancing_abouttostop do str = string.format("%d", index) simAddStatusbarMessage(str) index = index + 1 simSwitchThread() -- resume in next simulation stepend-- Put some clean-up code here
打开仿真设置对话框,修改仿真时间步长为1000ms,并在工具栏上点击按钮打开real-time模式:
开始仿真,可以看到状态栏会每隔1s依次输出0、1、2、3...等数字,这意味着threaded child script与main script的执行同步了。而如果在程序的while循环中将simSwitchThread()注释掉,再次进行仿真,则会在瞬间输出大量数字,即threaded child script比main script跑的要快...
另外需要注意的是必须开启real-time模式,否则仿真时也会瞬间就输出大量数字,因为正常模式下仿真会以极快的速度运行,与真实时间并不同步。
参考: