in winrt/lib/xaml/CanvasAnimatedControl.cpp [746:900]
void CanvasAnimatedControl::ChangedImpl()
{
//
// This is called when something has happened that should force the
// control to redraw itself. For example, DPI has changed, device has
// been lost, resources have been loaded.
//
// This method, as an action, is always run on the UI thread.
//
auto lock = Lock(m_sharedStateMutex);
bool needsDraw = m_sharedState.NeedsDraw || m_sharedState.Invalidated;
bool isPaused = m_sharedState.IsPaused;
bool hasPendingActions = !m_sharedState.PendingAsyncActions.empty();
lock.unlock();
//
// Get the status of the update/render thread.
//
ComPtr<IAsyncInfo> completedTickLoopInfo;
AsyncStatus tickLoopStatus = AsyncStatus::Completed;
if (m_gameLoop)
{
bool isRunning;
m_gameLoop->TakeTickLoopState(&isRunning, &completedTickLoopInfo);
if (isRunning)
{
// The tick loop is still running, so we don't do anything here.
// Ultimately, we rely on the tick loop noticing that something
// needs to change. When it does, it will stop itself and trigger
// another Changed().
return;
}
// The first time we consider starting the tick loop it won't be
// running, but we won't have a completedTickLoopInfo either.
if (completedTickLoopInfo)
{
ThrowIfFailed(completedTickLoopInfo->get_Status(&tickLoopStatus));
}
}
//
// Call Trim on application suspend, but only once we are sure the
// render thread is no longer running.
//
if (IsSuspended())
{
if (m_suspendingDeferral)
{
Trim();
ThrowIfFailed(m_suspendingDeferral->Complete());
m_suspendingDeferral.Reset();
}
return;
}
//
// Changed already checked IsLoaded before queueing this call to ChangedImpl,
// but we could have become unloaded between then and now, so must check again.
//
if (!IsLoaded())
return;
//
// Figure out if we should try and start the render loop.
//
bool ignorePaused = false;
if (tickLoopStatus != AsyncStatus::Completed)
{
// If the last render loop ended due to an error (eg lost device)
// then we want to process the error. The error is processed below
// where we try and start the render loop. In this context we can
// rethrow the exception that was marshaled from the render loop and
// have it caught and handled by RunWithRenderTarget.
ignorePaused = true;
}
if (needsDraw)
{
// If we've marked that we need to redraw (eg because the clear
// color has changed) then we'll need to start the loop.
ignorePaused = true;
}
if (hasPendingActions)
{
// If there are actions pending then we want to give the loop a chance
// to run those actions.
ignorePaused = true;
}
if (isPaused && !ignorePaused)
{
// Don't start the render loop if we're paused
return;
}
//
// The UI thread is about to manipulate device resources (eg it might be
// about to call CreateResources). While it is doing this we don't want
// anything running on the game loop thread, so we stop the dispatcher from
// processing events.
//
m_gameLoop->StopDispatcher();
//
// Try and start the update/render thread
//
RunWithRenderTarget(
[&] (CanvasSwapChain* target, ICanvasDevice*, Color const& clearColor, bool areResourcesCreated)
{
// The clearColor passed to us is ignored since this needs to be
// checked on each tick of the update/render loop.
UNREFERENCED_PARAMETER(clearColor);
//
// Process the results of the previous update/render loop. We
// do this here, within RunWithRenderTarget, so that it can
// handle errors (such as lost device).
//
if (completedTickLoopInfo)
{
HRESULT hr;
ThrowIfFailed(completedTickLoopInfo->get_ErrorCode(&hr));
ThrowIfFailed(hr);
}
if (areResourcesCreated)
{
// Once resources have been created we can start the dispatcher,
// since the game loop owns the device & resources at this
// point.
m_gameLoop->StartDispatcher();
}
if (!areResourcesCreated && !needsDraw)
{
//
// If resources aren't created then there's no point
// spinning up the update/render thread.
//
return;
}
m_gameLoop->StartTickLoop(target, areResourcesCreated);
});
}