上一次,我們可以獲取到圖片動(dòng)畫(huà)幀之間的時(shí)間間隔,如果想讓動(dòng)畫(huà)轉(zhuǎn)起來(lái),就必須有時(shí)鐘。插入的圖片動(dòng)畫(huà)數(shù)量可能會(huì)比較多,因此要想不影響性能,時(shí)鐘必須很輕量級(jí)而且要很高效。
Windows平臺(tái)上實(shí)現(xiàn)時(shí)鐘的方式五花八門(mén),你可以使用窗口相關(guān)的SetTimer來(lái)設(shè)置一個(gè)時(shí)鐘,也可以自己開(kāi)辟線程來(lái)做等待觸發(fā)模擬時(shí)鐘,而Chromium封裝的要更加C++對(duì)象化一些:依托Windows窗口消息,抽象出延遲任務(wù)的概念。這種手法幾年前我也曾經(jīng)考慮過(guò),只是對(duì)其中下次最短觸發(fā)時(shí)間計(jì)算以及更新的算法和設(shè)計(jì)都有力不從心,最終得出的是誤差很大的精簡(jiǎn)版:選擇固定的最小時(shí)間片為最小觸發(fā)單位,對(duì)很小的時(shí)間間隔誤差很明顯。
Windows有Timer Queues用來(lái)實(shí)現(xiàn)高效的異步時(shí)鐘,比較奇怪的是這組API用的貌似并不多。我們知道每個(gè)進(jìn)程都有一個(gè)默認(rèn)的線程池,可以在其中執(zhí)行一些Work Items,時(shí)鐘隊(duì)列和等待操作也都會(huì)用到這個(gè)線程池。timer-queue中的timers創(chuàng)建和銷(xiāo)毀都很輕量高效,因此我選擇了它。
每個(gè)OLE圖片對(duì)象在設(shè)置圖片之后,如果發(fā)現(xiàn)是多幀的,就需要啟動(dòng)動(dòng)畫(huà),創(chuàng)建時(shí)鐘:
ATLVERIFY(CreateTimerQueueTimer(&timer_, NULL,
WaitOrTimerCallback,
callback_parameter_.get(),
image_->GetFrameDelay(current_frame_),
0, WT_EXECUTEDEFAULT));
這里timer_是返回值,返回新建的時(shí)鐘對(duì)象,可以在OLE對(duì)象銷(xiāo)毀或者回調(diào)函數(shù)中進(jìn)行刪除,而刪除操作會(huì)等待回調(diào)執(zhí)行完畢才返回。傳遞TimerQueue為NULL表示使用系統(tǒng)的隊(duì)列。Period為0表示只觸發(fā)一次,觸發(fā)時(shí)間為image_->GetFrameDelay(current_frame_)。由于回調(diào)函數(shù)WaitOrTimerCallback是在線程池的線程中執(zhí)行,所以更新操作需要同步到動(dòng)畫(huà)圖片的創(chuàng)建線程中。callback_parameter_包含有上一節(jié)提及的ThreadState對(duì)象以及動(dòng)畫(huà)OLE對(duì)象指針,ThreadState創(chuàng)建的時(shí)候會(huì)同時(shí)創(chuàng)建一個(gè)隱藏窗口用于工作者線程向UI線程同步操作:
VOID CALLBACK IMRichPicture::WaitOrTimerCallback(PVOID lpParameter,
BOOLEAN TimerOrWaitFired) {
ATLASSERT(TimerOrWaitFired == TRUE);
IMRichPicture::CallbackParameter* parameter =
reinterpret_cast<IMRichPicture::CallbackParameter*>(lpParameter);
ATLASSERT(parameter);
parameter->thread_state->UpdatePictureFrame(parameter->picture);
}
下面是UpdatePictureFrame的實(shí)現(xiàn):
void IMThreadState::UpdatePictureFrame(IMRichPicture* picture) const {
PostMessage(message_window_, kMessageUpdatePictureFrame,
reinterpret_cast<WPARAM>(picture->richedit()),
reinterpret_cast<LPARAM>(picture));
}
這樣繞一大圈子,是為了利用Timer Queues的同時(shí)保證圖片的更新操作是在UI線程中執(zhí)行,因?yàn)閳D片被插入也是發(fā)生在UI線程,即動(dòng)畫(huà)控件創(chuàng)建于UI線程,為了避免加鎖帶來(lái)的麻煩以及死鎖的可能性,不應(yīng)該輕易去加鎖,盡量利用操作系統(tǒng)提供的基礎(chǔ)設(shè)施來(lái)實(shí)現(xiàn)。這里需要注意的是隱藏窗口接收到kMessageUpdatePictureFrame消息時(shí),richedit窗口可能已不存在或者動(dòng)畫(huà)控件已經(jīng)銷(xiāo)毀,因此使用指針前,需要判斷對(duì)象是否還存在:
case kMessageUpdatePictureFrame: {
IMRichEditImpl* richedit = reinterpret_cast<IMRichEditImpl*>(wparam);
IMRichPicture* picture = reinterpret_cast<IMRichPicture*>(lparam);
if (IMThreadState::current()->HasRichEdit(richedit))
richedit->OnUpdatePictureFrame(picture);
return 0;
}
posted on 2012-06-24 15:51
萬(wàn)連文 閱讀(2931)
評(píng)論(6) 編輯 收藏 引用 所屬分類(lèi):
richedit