void CanvasAnimatedControl::ChangedImpl()

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);
        });
}