接上篇的例子:haxe的nem与box2d入门例子
这个例子是将一个png的图片贴到小球上,并与小球一起做自由落体运动。
首先导入3个类包:
import nme.display.BitmapData;
import nme.display.Bitmap;
import nme.Assets;
首先创建一个Circle类:
/**
* 球体
*/
class Circle extends Sprite {
private var radius : Float;
private var wheel : Bitmap;
public function new(x : Float, y : Float, radius : Float) {
super();
this.radius = radius;
this.wheel = new Bitmap(Assets.getBitmapData('img/wheel.png'));
this.wheel.x = x - radius;
this.wheel.y = y - radius;
this.addChild(this.wheel);
}
public function move(position : B2Vec2) : Void {
//box2d以物体的中心为原点,而nme则以左上角为原点,所有要减去圆的半径
this.wheel.x = position.x / Settings.METERTOPIXEL - this.radius;
this.wheel.y = position.y / Settings.METERTOPIXEL - this.radius;
}
}
然后在createBall方法中加入:
//添加皮肤
var myCircle : Circle = new Circle(x, y, radius);
wheelBodyDef.userData = myCircle;
addChild(myCircle);
上面的代码要放在var wheelBody : B2Body = world.createBody(wheelBodyDef);之前。
按F5调试你会只会看到一个球体,这个球体自由落体后你就可以看到刚才加载的那张图片。
下面设置球体与皮肤的同步运动:
在update方法中加入同步更新位置:
var body : B2Body = world.getBodyList();
while (null != body) {
//body.m_type == 2时,物体为动态的
if (2 == body.m_type && Std.is(body.getUserData(), Sprite)) {
body.getUserData().move(body.getPosition());
}
body = body.getNext();
}
在init方法中把drawDebug()及update()方法中的world.drawDebugData();调用去掉,按F5,加载在图片在做自由落体运动。
最后给出这个例子的下载地址:
最后给出这个例子的下载地址:
共享链接:http://163.fm/TygF3Ie
提取码:1x1cZAxs
原文地址:http://gentwolf.sinaapp.com/index/detail/170.html
STM32的开发目前大多数还开处于“裸奔”的阶段,处于开发成本的考虑,可能还未嵌入任何的RTOS系统,由于没有操作系统的支持,因而不能方便的对多任务进行调度和管理,在main函数中你可能会写成如下方式:
int main(void) { while (1) { Task1(); // 调用任务1 Task2(); // 调用任务2 } }
但简单这样写的话会存在一个问题,假如任务1是一个很紧急的任务,如AD采样任务,需要不断的去执行,而任务2是一个不太紧急的任务,只要保证一段时间执行一次就行(如控制LED灯闪烁,只需要每1s钟闪烁一次),这样的话一是频繁的调用任务2占用了任务1执行的时间,二是任务2根本不需要这样频繁的执行,白白耗费了CPU的处理。因此可以考虑实现一个调度策略来解决这个问题。对于每个任务,我们可以定义这样一个结构:
typedef struct{ void (*fTask)(void); int64u uNextTick; int32u uLenTick; }sTask;
其中fTask为任务指针,指向具体的任务,uNextTick为该任务下一次执行的时间,uLenTick为任务的调度周期或叫调度频率,即每隔多长时间执行一次。
按照这个结构,可以预先定义一个结构体数组,然后将要调用的任务和任务的调度时间按照如下方式罗列出来:
// 任务列表 static sTask mTaskTab[] = { {Task_SysTick, 0, 0} ,{Task1, 0, 10} // 10ms执行一次 ,{Task2, 0, 200} // 200ms执行一次 };
其中第一个任务Task_SysTick为计算系统时间的任务,用以获取上电后运行的时间(Task_SysTick任务相关代码附在文章后面)。这里默认任务下一次执行的时间为0,在main函数中,不断的轮询这个数组,然后将当前任务的下一次调用时间和当前时间比较,如果发现轮到该任务执行,就执行该任务,执行完成后,将该任务的下一次执行时间设为当前时间加任务的调度时间,然后按照此方法去执行下一个需要执行的任务,代码如下:
while (1) { // 任务循环 for (i = 0; i < ARRAYSIZE(mTaskTab); i++) { if (mTaskTab[i].uNextTick <= GetTimingTick()) { mTaskTab[i].uNextTick += mTaskTab[i].uLenTick; mTaskTab[i].fTask(); } } }
这样,就可以对多个任务做一个简单的调度,以后添加任务时只需要在mTaskTab表中添加即可,需要强调的是,由于执行每个任务也需要耗费时间,就会导致一个任务的实际调度周期可能会比设定的调度周期要长,这样会存在时间不准的情况,当然这仅仅是适合于对轮询周期不是很严格的任务,如果想要任务在严格的时间周期内执行或者需要更精确的时间处理,则必须采用定时器的方式了。
附:
完整的main文件代码:
#ifndef ARRAYSIZE #define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0])) #endif // 任务结构 typedef struct{ void (*fTask)(void); u64 uNextTick; u32 uLenTick; }sTask; // 任务列表 static sTask mTaskTab[] = { {Task_SysTick, 0, 0} ,{Task1, 0, 10} // 10ms执行一次 ,{Task2, 0, 200} // 200ms执行一次 // 在这之前添加任务 }; /******************************************************************************* * Function Name : main * Description : Main program. * Input : None * Output : None * Return : None *******************************************************************************/ int main(void) { int i = 0; // 硬件初始化 HW_init(); // 初始化系统Tick任务 dev_SysTick_init(void); // ... while (1) { // 任务循环 for (i = 0; i < ARRAYSIZE(mTaskTab); i++) { if (mTaskTab[i].uNextTick <= GetTimingTick()) { mTaskTab[i].uNextTick += mTaskTab[i].uLenTick; mTaskTab[i].fTask(); } } }}
Task_SysTick任务相关代码:
volatile int64u g_TimingTick = 0; volatile int64u g_TimingTickOld = 0; //================================================================================================= //【函 数 名 称】 void dev_SysTick_init(void) //【参 数】 //【功 能】 初始化 //【返 回 值】 None //【创 建 者】 2010-07-27 firehood //================================================================================================= void dev_SysTick_init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /* Time base configuration */ TIM_TimeBaseStructure.TIM_Period = 65535; TIM_TimeBaseStructure.TIM_Prescaler = 36000-1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_SetCounter(TIM2, 0); /* TIM enable counter */ TIM_Cmd(TIM2, ENABLE); } //================================================================================================= //【函 数 名 称】 void GetTimingTick(void) //【参 数】 //【功 能】 获取MCU启动后的运行时间 //【返 回 值】 MCU启动后的运行时间,单位ms //【创 建 者】 2010-07-27 firehood //================================================================================================= int64u GetTimingTick(void) { return g_TimingTick; } //================================================================================================= //【函 数 名 称】 void Task_SysTick(void) //【参 数】 //【功 能】 Tick任务,从TIM2获取系统时间 //【返 回 值】 None //【创 建 者】 2010-07-27 firehood //================================================================================================= void Task_SysTick(void) { int16u temp = TIM_GetCounter(TIM2); if (temp > 1000) { TIM_SetCounter(TIM2, 0); g_TimingTickOld = g_TimingTickOld + temp; temp = 0; } g_TimingTick = g_TimingTickOld + temp; }
gstreamer的官方文档里,调度的英文是schedule. 什么是schedule? 它的英文解释为" to plan that something will happen at a particular time “.在gstreamer里,调度的意义跟它基本差不多。在gstreamer里,调度的目的主要有以下几个:
(1)在适当的时候通知每个元件去完成相应的任务,确保这些职责的执行。这个任务,就是数据处理。
(2)每个元件都从它的上一个源元件获取数据,并为下一个元件准备好数据。即获取输入数据,处理数据,最后输出数据给下一个元件。
在GStreamer中,衬垫(pad)是用来在元件间协商连接和数据流的。衬垫可以看作元件间互相连接的“接口”,数据流通过这些接口流入流出元件。Pad具有特殊的数据处理能力:衬垫可以限制通过它的数据类型。只有当两个衬垫允许通过的数据类型兼容时才可以将它们连接起来。
第一个source元件,只有src衬垫,即只输出数据;最后一个sink(输出)元件,即只接收输入数据;管道中其他元件,一般都有一个sink衬垫和source衬垫。
Gstreamer的调度,也可以理解为不同元件的pad连接和通信,即处理pad之间的数据流的启动,暂停,继续和终止。
调度模式有两种:PUSH模式和PULL模式。在此,我将其翻译为推送模式和拉拽模式。
1.什么是推送模式和拉拽模式?
Downstream(下游) 和 Upstream(上游)模式是管道中用于描述数据流动方向的术语。从source元件到sink元件的数据流动称作“下游”,从sink元件到source元件的数据流动模式称为“上游”模式。
在推送模式下,上游元件通过调用gst_pad_push,实现把数据“推送”给下游元件。
在拉拽模式下,下游元件通过请求调用gst_pad_pull_range(),从上游组件那里把数据“拉”过来。
上面提到的数据,一般是一个缓存(buffer).
用得最多的数据流模式是推送模式。拉拽模式可以用在某些特殊情况下,如demuxer元件。
2.推送模式是怎么工作的?
.----------. .----------. .----------.
| element1 | | element2 | | element3 |
... src -> sink src -> sink ...
'----------' '----------' '----------'
如上图所示,假设元件1的src pad工作在push模式。当元件1打算把数据推送给下一个与之相连接的元件2时,元件1的src pad通常产生数据,元件2的相应sink pad(相对于元件1,被称为peer pad)接受数据。通常,元件1会实现一个loop函数,该函数会被循环调用直到它返回值为假。该函数可以根据需要进行阻塞循环调用,但当元件1的pad解除激活时,该函数就解除阻塞(即不再调用)。
元件2的pad通过实现一个链条(chain)函数来接受元件1的pad生产的数据。
push模式的实现是由元件1通过gst_pad_push()来实现的. 产生数据的元件pad, 注意:这个方法根据传入的pad参数,具体调用相应的链接函数xx_chain。该方法的函数原型是GstFlowReturngst_pad_push (GstPad * pad, GstBuffer * buffer)。在该函数的实现里,通过调用 GST_PAD_CHAINFUNC (peer) (peer, buffer)来把数据推送给下一个元件,同时下一个元件根据他来接受数据。
3.拉拽模式是怎么工作的?
工作在PULL模式的pad, 通常只能从实现了pull_range函数的pad那里“拉”数据。
.----------. .----------. .----------. .----------.
| element1 | | element2 | | demuxer| |decoder|
... src -> sink src -> sink src -> sink ...
'----------' '----------' '----------' ----------'
如上图所示,我们假设demuxer元件的sink pad工作在pull模式,并是以plugin的形式加载到工作管道中。在demuxer对象实例初始化时候,我们一般首先会新建和初始化一个sink pad,并通过调用gst_pad_set_activate_function (avi->sinkpad, gst_xx_demux_sink_activate)来设置pad激活时的函数指针。于是,当pipeline状态变化时,尤其是demuxer元件从READY切换到PAUSE状态时,会进行如下图所示的函数调用:
在上图的激活函数里,会去激活demuxer 的sink pad, 它是通过遍历所有的sink pads,调用activate_pads来激活pad的。如下图所示:
在gst_pad_set_actie函数里,最后会调用(GST_PAD_ACTIVATEFUNC(pad)) (pad); 被调用这个宏函数由于在demuxer元件里设置了函数指针gst_pad_set_activate_function (avi->sinkpad, gst_xx_demux_sink_activate),于是其实对于demuxer组件来说,执行的是gst_xx_demux_sink_activate函数。
通常,这个函数会调用gst_pad_check_pull_range,并根据前面图中的element2的pad(称之为peer pad)是否实现了XX_pull_range函数,来判断demuxer 元件的sink pad应该激活在pull模式还是push模式。我们这里讨论激活在pull模式的情况。
当激活在pull模式下,我们会给demux起一个task, task函数为gst_xx_demux_loop函数。gst_avi_demux_loop函数会从peer pad 拉拽数据。拉拽完数据后,通常会把数据push 到下一个元件的sink pad.
4.调度模式是如何动态决定的
这个问题的答案可以在上面第3节找到找到。这里总结下:当一个pad被激活时,gst_element_pad_activate()函数被调用. 这个pad随后可以根据上游的能力(upstream capabilities,实际上为是否实现了XX_pull_range)来决定激活在push模式还是pull模式。如果一个pad没有activate函数,gstreamer core将默认为它激活在push模式。
5.getrange函数
当gst_pad_check_pull_range调用时,peer pad的getrange函数被调用。事实上,前面说过,一个上游pad只有实现了getrange函数,才能被下游的pad以pull的模式调用它从而拉拽数据。下游pad对getrange函数的调用,是通过以宏的形式---GST_PAD_GETRANGEFUNC (pad)来传递的。而该宏的赋值,是上游的可以支持pull模式的元件在初始化的时候,调用gst_pad_set_getrange_function 设置的。
6.chain函数(链条函数)
当上游元件的pad调用gst_pad_push函数时,下游元件pad的chain函数被调用。