分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
requestAnimationFrame是HTML5游戏和动画必不可少的函数,相对于setTimeout或setInterval它有两个优势,一是它注册的回调函数与浏览器的渲染同步,不用担心Timer的时间间隔太长或太短。二是时间间隔相对与Timer要稳定,requestAnimationFrame注册的回调函数最高执行频率是60FPS,虽然在HTML5游戏里通常是达不到的,但是它两次调用之间时间间隔要比Timer稳定一些。前段时间我在CanTK Runtime里自己模拟过requestAnimationFrame,为了深入的理解Chrome里实现requestAnimationFrame方法,花了一点时间去读Blink的代码。
requestAnimationFrame的基本用法如下:
var start = null;var element = document.getElementById("SomeElementYouWantToAnimate");function step(timestamp) { if (!start) start = timestamp; var progress = timestamp - start; element.style.left = Math.min(progress/10, 200) + "px"; if (progress < 2000) { window.requestAnimationFrame(step); }}window.requestAnimationFrame(step);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我比较关注的是回调函数的注册和调用过程:
- 1.注册回调函数的实现。
(WebKit/Source/core/dom/ScriptedAnimationController.cpp)
ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(FrameRequestCallback* callback){ CallbackId id = m_callbackCollection.registerCallback(callback); scheduleAnimationIfNeeded(); return id;}
- 1
- 2
- 3
- 4
- 5
- 6
注册之后还需要请求重绘,scheduleAnimationIfNeeded最终会调到ThreadProxy::SendCommitRequestToImplThreadIfNeeded:
(cc/trees/thread_proxy.cc)
bool ThreadProxy::SendCommitRequestToImplThreadIfNeeded( CommitPipelineStage required_stage) { DCHECK(IsMainThread()); DCHECK_NE(NO_PIPELINE_STAGE, required_stage); bool already_posted = main().max_requested_pipeline_stage != NO_PIPELINE_STAGE; main().max_requested_pipeline_stage = std::max(main().max_requested_pipeline_stage, required_stage); if (already_posted) return false; Proxy::ImplThreadTaskRunner()->PostTask( FROM_HERE, base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread, impl_thread_weak_ptr_)); return true;}void ThreadProxy::SetNeedsCommitOnImplThread() { TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommitOnImplThread"); DCHECK(IsImplThread()); impl().scheduler->SetNeedsBeginMainFrame();}void Scheduler::SetNeedsBeginMainFrame() { state_machine_.SetNeedsBeginMainFrame(); ProcessScheduledActions();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 2.执行回调函数的实现。
WebKit/Source/core/dom/ScriptedAnimationController.cpp
void ScriptedAnimationController::executeCallbacks(double monotonicTimeNow){ // dispatchEvents() runs script which can cause the document to be destroyed. if (!m_document) return; double highResNowMs = 1000.0 * m_document->loader()->timing().monotonicTimeToZeroBasedDocumentTime(monotonicTimeNow); double legacyHighResNowMs = 1000.0 * m_document->loader()->timing().monotonicTimeToPseudoWallTime(monotonicTimeNow); m_callbackCollection.executeCallbacks(highResNowMs, legacyHighResNowMs);}void FrameRequestCallbackCollection::executeCallbacks(double highResNowMs, double highResNowMsLegacy){ // First, generate a list of callbacks to consider. Callbacks registered from this point // on are considered only for the "next" frame, not this one. ASSERT(m_callbacksToInvoke.isEmpty()); m_callbacksToInvoke.swap(m_callbacks); for (size_t i = 0; i < m_callbacksToInvoke.size(); ++i) { FrameRequestCallback* callback = m_callbacksToInvoke[i].get(); if (!callback->m_cancelled) { TRACE_EVENT1("devtools.timeline", "FireAnimationFrame", "data", InspectorAnimationFrameEvent::data(m_context, callback->m_id)); InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(m_context, callback->m_id); if (callback->m_useLegacyTimeBase) callback->handleEvent(highResNowMsLegacy); else callback->handleEvent(highResNowMs); InspectorInstrumentation::didFireAnimationFrame(cookie); TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data", InspectorUpdateCountersEvent::data()); } } m_callbacksToInvoke.clear();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
这行代码m_callbacksToInvoke.swap(m_callbacks);比较有意思,在循环执行的callbacks会有新的callback注册进来,我在实现这个功能时,没看过这段代码,当时费了点功夫才想明白怎么搞。
上面的代码是由RenderWidgetCompositor::BeginMainFrame调过来的:
(src/content/renderer/gpu/render_widget_compositor.cc)
void RenderWidgetCompositor::BeginMainFrame(const cc::BeginFrameArgs& args) { double frame_time_sec = (args.frame_time - base::TimeTicks()).InSecondsF(); double deadline_sec = (args.deadline - base::TimeTicks()).InSecondsF(); double interval_sec = args.interval.InSecondsF(); WebBeginFrameArgs web_begin_frame_args = WebBeginFrameArgs(frame_time_sec, deadline_sec, interval_sec); compositor_deps_->GetRendererScheduler()->WillBeginFrame(args); widget_->webwidget()->beginFrame(web_begin_frame_args);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9