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