//-------------------------------------------------------------------------------------- // CoroutinesXDK.cpp // // Advanced Technology Group (ATG) // Copyright (C) Microsoft Corporation. All rights reserved. //-------------------------------------------------------------------------------------- #include "pch.h" #include "CoroutinesXDK.h" #include "ATGColors.h" #include "ControllerFont.h" extern void ExitSample() noexcept; using namespace DirectX; using namespace Windows::Xbox::UI; using Microsoft::WRL::ComPtr; namespace { // This is a simple awaitable type that allows you to query the status of an asynchronous operation and // resume a coroutine once that operation is complete struct keyboard_await_adapter { Windows::Foundation::IAsyncOperation^ async; bool await_ready() { return async->Status == Windows::Foundation::AsyncStatus::Completed; } void await_suspend(std::experimental::coroutine_handle<>) const { } auto await_resume() const { return async->GetResults(); } }; // Global co_await operator allows you to use co_await with any type and transform it to another awaitable type keyboard_await_adapter operator co_await(Windows::Foundation::IAsyncOperation ^ async) { return { async }; } // This awaitable type allows you to resume an IAsyncOperation on a background thread // once it has completed. struct accountpicker_await_adapter { Windows::Foundation::IAsyncOperation^ async; bool await_ready() const { return async->Status == Windows::Foundation::AsyncStatus::Completed; } void await_suspend(std::experimental::coroutine_handle<> handle) const { winrt::com_ptr context; winrt::check_hresult(CoGetObjectContext(__uuidof(context), reinterpret_cast(winrt::put_abi(context)))); async->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler([handle, context = std::move(context)](const auto, Windows::Foundation::AsyncStatus) { ComCallData data = {}; data.pUserDefined = handle.address(); auto callback = [](ComCallData * data) { std::experimental::coroutine_handle<>::from_address(data->pUserDefined)(); return S_OK; }; winrt::check_hresult(context->ContextCallback(callback, &data, IID_ICallbackWithNoReentrancyToApplicationSTA, 5, nullptr)); }); } auto await_resume() const { return async->GetResults(); } }; // Global co_await operator allows you to use co_await with any type and transform it to another awaitable type accountpicker_await_adapter operator co_await(Windows::Foundation::IAsyncOperation ^ async) { return { async }; } } Sample::Sample() : m_frame(0) { m_deviceResources = std::make_unique(); } // Initialize the Direct3D resources required to run. void Sample::Initialize(IUnknown* window) { m_gamePad = std::make_unique(); m_deviceResources->SetWindow(window); m_deviceResources->CreateDeviceResources(); CreateDeviceDependentResources(); m_deviceResources->CreateWindowSizeDependentResources(); CreateWindowSizeDependentResources(); m_stringAsync = nullptr; m_acquiringNewUser = false; } std::future Sample::UpdateUsersOffThread() { // Start on the same thread as the caller EmitThreadIdDebug(); // This type will resume immediately on another thread co_await winrt::resume_background{}; std::lock_guard lock(m_usersMutex); // This is a long operation so do it on a thread that isn't rendering m_users = Windows::Xbox::System::User::Users; EmitThreadIdDebug(); } std::future Sample::GetNewUser() { m_acquiringNewUser = true; auto op = SystemUI::ShowAccountPickerAsync(nullptr, AccountPickerOptions::None); // This requires a local variable for string formatting. Do it in a subroutine // so it is not a part of the coroutine context allocation. EmitThreadIdDebug(); // This coroutine will suspend here and return execution to the caller. Once the // account picker operation is complete the coroutine will resume on a background // thread because this is using the overloaded co_await operator that transforms // an AccountPickerResult^ into our custom accountpicker_await_adapter object. // Once the operation is complete, all locals and variable declared before this // statement, such as the AccountPickerResult^ object itself, are accessible. co_await op; // This part of the coroutine is running on a different thread. EmitThreadIdDebug(); AccountPickerResult^ results = nullptr; try { results = op->GetResults(); // lock guard is required since m_currentUser is accessed across multiple threads std::lock_guard lock(m_userMutex); m_currentUser = results->User; } catch (...) { // GetResults can throw an exception if the TCUI failed for some reason. There isn't much // for this sample to do. It should be handled in a way that's appropriate to game code. } m_acquiringNewUser = false; co_return results; } awaitable_future Sample::GetNewDisplayString() { m_displayString = nullptr; auto async = SystemUI::ShowVirtualKeyboardAsync(L"String", L"Virtual Keyboard", L"Provide a string", VirtualKeyboardInputScope::Default); m_stringAsync = async; auto newString = co_await async; m_displayString = newString; co_return newString; } // This is a normal subroutine which means local variables will be put on the stack // instead of part of an allocation on the heap. void Sample::EmitThreadIdDebug() { wchar_t buffer[100]; swprintf_s(buffer, L"Current thread ID: %i\n", GetCurrentThreadId()); OutputDebugString(buffer); } #pragma region Frame Update // Executes basic render loop. void Sample::Tick() { PIXBeginEvent(PIX_COLOR_DEFAULT, L"Frame %llu", m_frame); m_timer.Tick([&]() { Update(m_timer); }); Render(); PIXEndEvent(); m_frame++; } // Updates the world. void Sample::Update(DX::StepTimer const&) { PIXBeginEvent(PIX_COLOR_DEFAULT, L"Update"); auto pad = m_gamePad->GetState(0); if (pad.IsConnected()) { m_gamePadButtons.Update(pad); if (m_gamePadButtons.a) { if(!m_acquiringNewUser) GetNewUser(); } if (m_gamePadButtons.x) { UpdateUsersOffThread(); } if (m_gamePadButtons.y) { m_displayString = nullptr; m_future = GetNewDisplayString(); } if (pad.IsViewPressed()) { ExitSample(); } } else { m_gamePadButtons.Reset(); } PIXEndEvent(); } #pragma endregion #pragma region Frame Render // Draws the scene. void Sample::Render() { // Don't try to render anything before the first Update. if (m_timer.GetFrameCount() == 0) { return; } // Prepare the render target to render a new frame. m_deviceResources->Prepare(); Clear(); auto context = m_deviceResources->GetD3DDeviceContext(); PIXBeginEvent(context, PIX_COLOR_DEFAULT, L"Render"); m_spriteBatch->Begin(); float posx = 60.f; float posy = 20.f; wchar_t buffer[2000] = {}; { // lock guard is required since m_currentUser is accessed across multiple threads std::lock_guard lock(m_userMutex); swprintf_s(buffer, L"Press [A] to change current user\nPress [X] to query users\nPress [Y] to open virtual keyboard\nCurrent user: %ls", (m_currentUser == nullptr ? L"" : m_currentUser->DisplayInfo->Gamertag->Begin())); } DX::DrawControllerString(m_spriteBatch.get(), m_font.get(), m_ctrlFont.get(), buffer, DirectX::XMFLOAT2{ posx, posy }, DirectX::Colors::White); posy += 200.f; if (m_users != nullptr) { m_font->DrawString(m_spriteBatch.get(), L"Users:", DirectX::XMFLOAT2{ posx, posy }, DirectX::Colors::White); for (uint32 i = 0; i < m_users->Size; ++i) { posy += 35.f; Platform::String^ display = m_users->GetAt(i)->DisplayInfo->Gamertag; m_font->DrawString(m_spriteBatch.get(), display->Begin(), DirectX::XMFLOAT2{ posx, posy }, DirectX::Colors::White); } } // Poll for completion of virtual keyboard event in the string coroutine. Once it is completed // the coroutine can be resumed to finish the process. if (m_displayString == nullptr) { if (m_stringAsync != nullptr) { if (m_stringAsync->Status == Windows::Foundation::AsyncStatus::Completed) { m_future.resume(); m_future.release(); m_stringAsync = nullptr; } } } else { m_font->DrawString(m_spriteBatch.get(), m_displayString->Begin(), DirectX::XMFLOAT2{ 640.f, 20.f }, DirectX::Colors::White); } m_spriteBatch->End(); PIXEndEvent(context); // Show the new frame. PIXBeginEvent(context, PIX_COLOR_DEFAULT, L"Present"); m_deviceResources->Present(); m_graphicsMemory->Commit(); PIXEndEvent(context); } // Helper method to clear the back buffers. void Sample::Clear() { auto context = m_deviceResources->GetD3DDeviceContext(); PIXBeginEvent(context, PIX_COLOR_DEFAULT, L"Clear"); // Clear the views. auto renderTarget = m_deviceResources->GetRenderTargetView(); auto depthStencil = m_deviceResources->GetDepthStencilView(); context->ClearRenderTargetView(renderTarget, ATG::Colors::Background); context->ClearDepthStencilView(depthStencil, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); context->OMSetRenderTargets(1, &renderTarget, depthStencil); // Set the viewport. auto viewport = m_deviceResources->GetScreenViewport(); context->RSSetViewports(1, &viewport); PIXEndEvent(context); } #pragma endregion #pragma region Message Handlers // Message handlers void Sample::OnSuspending() { auto context = m_deviceResources->GetD3DDeviceContext(); context->Suspend(0); } void Sample::OnResuming() { auto context = m_deviceResources->GetD3DDeviceContext(); context->Resume(); m_timer.ResetElapsedTime(); m_gamePadButtons.Reset(); } #pragma endregion #pragma region Direct3D Resources // These are the resources that depend on the device. void Sample::CreateDeviceDependentResources() { auto device = m_deviceResources->GetD3DDevice(); auto context = m_deviceResources->GetD3DDeviceContext(); m_graphicsMemory = std::make_unique(device, m_deviceResources->GetBackBufferCount()); m_font = std::make_unique(device, L"SegoeUI_24.spritefont"); m_ctrlFont = std::make_unique(device, L"XboxOneControllerLegendSmall.spritefont"); m_spriteBatch = std::make_unique(context); } // Allocate all memory resources that change on a window SizeChanged event. void Sample::CreateWindowSizeDependentResources() { auto viewport = m_deviceResources->GetScreenViewport(); m_spriteBatch->SetViewport(viewport); } #pragma endregion