Index: source/graphics/MapReader.cpp =================================================================== --- source/graphics/MapReader.cpp +++ source/graphics/MapReader.cpp @@ -1331,11 +1331,6 @@ { std::atomic progress{1}; Future task; - - ~GeneratorState() - { - task.CancelOrWait(); - } }; int CMapReader::StartMapGeneration(const CStrW& scriptFile) Index: source/graphics/TextureConverter.cpp =================================================================== --- source/graphics/TextureConverter.cpp +++ source/graphics/TextureConverter.cpp @@ -311,16 +311,7 @@ #endif // CONFIG2_NVTT } -CTextureConverter::~CTextureConverter() -{ -#if CONFIG2_NVTT - while (!m_ResultQueue.empty()) - { - m_ResultQueue.front().CancelOrWait(); - m_ResultQueue.pop(); - } -#endif // CONFIG2_NVTT -} +CTextureConverter::~CTextureConverter() = default; bool CTextureConverter::ConvertTexture(const CTexturePtr& texture, const VfsPath& src, const VfsPath& dest, const Settings& settings) { Index: source/ps/Future.h =================================================================== --- source/ps/Future.h +++ source/ps/Future.h @@ -57,12 +57,7 @@ {} ~Receiver() { - // For safety, wait on started task completion, but not on pending ones (auto-cancelled). - if (!Cancel()) - { - Wait(); - Cancel(); - } + ENSURE(IsDoneOrCanceled()); } Receiver(const Receiver&) = delete; @@ -145,7 +140,7 @@ * * Future is _not_ thread-safe. Call it from a single thread or ensure synchronization externally. * - * The destructor is never blocking. The promise may still be running on destruction. + * The callback never runs after the @p Future is destroyed. * TODO: * - Handle exceptions. */ @@ -162,11 +157,19 @@ Future() = default; Future(const Future& o) = delete; Future(Future&&) = default; - Future& operator=(Future&&) = default; - ~Future() = default; + Future& operator=(Future&& other) + { + CancelOrWait(); + m_Receiver = std::move(other.m_Receiver); + return *this; + } + ~Future() + { + CancelOrWait(); + } /** - * Make the future wait for the result of @a func. + * Make the future wait for the result of @a callback. */ template PackagedTask Wrap(Callback&& callback); @@ -293,6 +296,7 @@ { static_assert(std::is_same_v, ResultType>, "The return type of the wrapped function is not the same as the type the Future expects."); + CancelOrWait(); auto temp = std::make_shared>(std::move(callback)); m_Receiver = {temp, &temp->receiver}; return PackagedTask(std::move(temp)); Index: source/ps/tests/test_Future.h =================================================================== --- source/ps/tests/test_Future.h +++ source/ps/tests/test_Future.h @@ -19,7 +19,11 @@ #include "ps/Future.h" +#include +#include #include +#include +#include #include class TestFuture : public CxxTest::TestSuite @@ -27,22 +31,70 @@ public: void test_future_basic() { - int counter = 0; + bool executed{false}; + Future noret; + auto task = noret.Wrap([&]{ executed = true; }); + task(); + TS_ASSERT(executed); + } + + template + static void CancelationTestHelper(Wait wait) + { + using namespace std::literals; + constexpr auto minTaskDuration = 5560us; + + std::atomic taskStarted{false}; + std::optional> future{std::make_optional>()}; + auto task = future->Wrap([&] + { + taskStarted.store(true); + std::this_thread::sleep_for(minTaskDuration); + }); + const auto startTime = std::chrono::steady_clock::now(); + std::thread thread{task}; + // Wait some time to make sure the thread started the task. + while (!taskStarted.load()); + std::invoke(wait, future); + const auto stopTime = std::chrono::steady_clock::now(); + + // The task took longer then 5560us... + TS_ASSERT_LESS_THAN(minTaskDuration, stopTime - startTime); + // ...but not much. That depends on the scheduler. + TS_ASSERT_LESS_THAN(stopTime - startTime, minTaskDuration * 4); + thread.join(); + } + + void test_cancelation() + { { - Future noret; - std::function task = noret.Wrap([&counter]() mutable { counter++; }); + int executed{false}; + auto task = [&] + { + Future noret; + return noret.Wrap([&]{ executed = true; }); + // Auto-cancels the task. + }(); + // Even though the task is invoked, the callback doesn't get called. task(); - TS_ASSERT_EQUALS(counter, 1); + TS_ASSERT(!executed); } - { - Future noret; + // There are many ways how to wait on a future. + CancelationTestHelper(&Future::Wait); + CancelationTestHelper(&Future::CancelOrWait); + CancelationTestHelper([](std::optional>& future) { - std::function task = noret.Wrap([&counter]() mutable { counter++; }); - // Auto-cancels the task. - } - } - TS_ASSERT_EQUALS(counter, 1); + future->Wrap([]{}); + }); + CancelationTestHelper([](std::optional>& future) + { + *future = {}; + }); + CancelationTestHelper([](std::optional>& future) + { + future.reset(); + }); } void test_future_return() Index: source/simulation2/components/CCmpPathfinder.cpp =================================================================== --- source/simulation2/components/CCmpPathfinder.cpp +++ source/simulation2/components/CCmpPathfinder.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -105,9 +105,6 @@ { SetDebugOverlay(false); // cleans up memory - // Wait on all pathfinding tasks. - for (Future& future : m_Futures) - future.CancelOrWait(); m_Futures.clear(); SAFE_DELETE(m_AtlasOverlay);