wraitii
1b35d36daa
Tasks are simple callables (e.g. lambdas), and can be pushed with 2 priority levels. Pushing a task returns a future. Futures can be waited on, can return results, and can be cancelled deterministically. Futures can also not be waited on. This gives 'hardware concurrency - 1' threads to maximize CPU usage in a work-stealing workflow. Reviewed by: vladislavbelov Refs #5874 Differential Revision: https://code.wildfiregames.com/D3848 This was SVN commit r25656.
137 lines
3.6 KiB
C++
137 lines
3.6 KiB
C++
/* Copyright (C) 2021 Wildfire Games.
|
|
* This file is part of 0 A.D.
|
|
*
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "lib/self_test.h"
|
|
|
|
#include "ps/Future.h"
|
|
|
|
#include <functional>
|
|
#include <type_traits>
|
|
|
|
class TestFuture : public CxxTest::TestSuite
|
|
{
|
|
public:
|
|
void test_future_basic()
|
|
{
|
|
int counter = 0;
|
|
{
|
|
Future<void> noret;
|
|
std::function<void()> task = noret.Wrap([&counter]() mutable { counter++; });
|
|
task();
|
|
TS_ASSERT_EQUALS(counter, 1);
|
|
}
|
|
|
|
{
|
|
Future<void> noret;
|
|
{
|
|
std::function<void()> task = noret.Wrap([&counter]() mutable { counter++; });
|
|
// Auto-cancels the task.
|
|
}
|
|
}
|
|
TS_ASSERT_EQUALS(counter, 1);
|
|
}
|
|
|
|
void test_future_return()
|
|
{
|
|
{
|
|
Future<int> future;
|
|
std::function<void()> task = future.Wrap([]() { return 1; });
|
|
task();
|
|
TS_ASSERT_EQUALS(future.Get(), 1);
|
|
}
|
|
|
|
// Convertible type.
|
|
{
|
|
Future<int> future;
|
|
std::function<void()> task = future.Wrap([]() -> u8 { return 1; });
|
|
task();
|
|
TS_ASSERT_EQUALS(future.Get(), 1);
|
|
}
|
|
|
|
static int destroyed = 0;
|
|
// No trivial constructor or destructor. Also not copiable.
|
|
struct NonDef
|
|
{
|
|
NonDef() = delete;
|
|
NonDef(int input) : value(input) {};
|
|
NonDef(const NonDef&) = delete;
|
|
NonDef(NonDef&& o)
|
|
{
|
|
value = o.value;
|
|
o.value = 0;
|
|
}
|
|
~NonDef() { if (value != 0) destroyed++; }
|
|
int value = 0;
|
|
};
|
|
TS_ASSERT_EQUALS(destroyed, 0);
|
|
{
|
|
Future<NonDef> future;
|
|
std::function<void()> task = future.Wrap([]() { return 1; });
|
|
task();
|
|
TS_ASSERT_EQUALS(future.Get().value, 1);
|
|
}
|
|
TS_ASSERT_EQUALS(destroyed, 1);
|
|
{
|
|
Future<NonDef> future;
|
|
std::function<void()> task = future.Wrap([]() { return 1; });
|
|
}
|
|
TS_ASSERT_EQUALS(destroyed, 1);
|
|
/**
|
|
* TODO: find a way to test this
|
|
{
|
|
Future<NonDef> future;
|
|
std::function<void()> task = future.Wrap([]() { return 1; });
|
|
future.Cancel();
|
|
future.Wait();
|
|
TS_ASSERT_THROWS(future.Get(), const Future<NonDef>::BadFutureAccess&);
|
|
}
|
|
*/
|
|
TS_ASSERT_EQUALS(destroyed, 1);
|
|
}
|
|
|
|
void test_future_moving()
|
|
{
|
|
Future<int> future;
|
|
std::function<int()> function;
|
|
|
|
// Set things up so all temporaries passed into the futures will be reset to obviously invalid memory.
|
|
std::aligned_storage_t<sizeof(Future<int>), alignof(Future<int>)> futureStorage;
|
|
std::aligned_storage_t<sizeof(std::function<int()>), alignof(std::function<int()>)> functionStorage;
|
|
Future<int>* f = &future; // CppCheck doesn't read placement new correctly, do this to silence errors.
|
|
std::function<int()>* c = &function;
|
|
f = new (&futureStorage) Future<int>{};
|
|
c = new (&functionStorage) std::function<int()>{};
|
|
|
|
*c = []() { return 7; };
|
|
std::function<void()> task = f->Wrap(std::move(*c));
|
|
|
|
future = std::move(*f);
|
|
function = std::move(*c);
|
|
|
|
// Destroy and clear the memory
|
|
f->~Future();
|
|
c->~function();
|
|
memset(&futureStorage, 0xFF, sizeof(decltype(futureStorage)));
|
|
memset(&functionStorage, 0xFF, sizeof(decltype(functionStorage)));
|
|
|
|
// Let's move the packaged task while at it.
|
|
std::function<void()> task2 = std::move(task);
|
|
task2();
|
|
TS_ASSERT_EQUALS(future.Get(), 7);
|
|
}
|
|
};
|