Index: ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 26915)
+++ ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 26916)
@@ -1,305 +1,306 @@
{
"Title": "Programming",
"Content": [
{
"Title": "Programming managers",
"List": [
{ "nick": "Acumen", "name": "Stuart Walpole" },
{ "nick": "Dak Lozar", "name": "Dave Loeser" },
{ "nick": "h20", "name": "Daniel Wilhelm" },
{ "nick": "Janwas", "name": "Jan Wassenberg" },
{ "nick": "Raj", "name": "Raj Sharma" }
]
},
{
"Subtitle": "Special thanks to",
"List": [
{ "nick": "leper", "name": "Georg Kilzer" },
{ "nick": "Ykkrosh", "name": "Philip Taylor" }
]
},
{
"List": [
{ "nick": "01d55" },
{ "nick": "aBothe", "name": "Alexander Bothe" },
{ "nick": "Acumen", "name": "Stuart Walpole" },
{ "nick": "adrian", "name": "Adrian Boguszewszki" },
{ "name": "Adrian Fatol" },
{ "nick": "AI-Amsterdam" },
{ "nick": "Alan", "name": "Alan Kemp" },
{ "nick": "Alex", "name": "Alexander Yakobovich" },
{ "nick": "alpha123", "name": "Peter P. Cannici" },
{ "nick": "alre" },
{ "nick": "Ampaex", "name": "Antonio Vazquez" },
{ "name": "André Puel" },
{ "nick": "andy5995", "name": "Andy Alt" },
{ "nick": "Angen" },
{ "nick": "Arfrever", "name": "Arfrever Frehtes Taifersar Arahesis" },
{ "nick": "ArnH", "name": "Arno Hemelhof" },
{ "nick": "Aurium", "name": "Aurélio Heckert" },
{ "nick": "azayrahmad", "name": "Aziz Rahmad" },
{ "nick": "badmadblacksad", "name": "Martin F" },
{ "nick": "badosu", "name": "Amadeus Folego" },
{ "nick": "bb", "name": "Bouke Jansen" },
{ "nick": "Bellaz89", "name": "Andrea Bellandi" },
{ "nick": "Ben", "name": "Ben Vinegar" },
{ "nick": "Bird" },
{ "nick": "Blue", "name": "Richard Welsh" },
{ "nick": "bmwiedemann" },
{ "nick": "boeseRaupe", "name": "Michael Kluge" },
{ "nick": "bog_dan_ro", "name": "BogDan Vatra" },
{ "nick": "Bonk", "name": "Christopher Ebbert" },
{ "nick": "Boudica" },
{ "nick": "Caius", "name": "Lars Kemmann" },
{ "nick": "Calefaction", "name": "Matt Holmes" },
{ "nick": "Calvinh", "name": "Carl-Johan Höiby" },
{ "nick": "causative", "name": "Bart Parkis" },
{ "name": "Cédric Houbart" },
{ "nick": "Ceres" },
{ "nick": "Chakakhan", "name": "Kenny Long" },
{ "nick": "Clockwork-Muse", "name": "Stephen A. Imhoff" },
{ "nick": "cpc", "name": "Clément Pit-Claudel" },
{ "nick": "Cracker78", "name": "Chad Heim" },
{ "nick": "Crynux", "name": "Stephen J. Fewer" },
{ "nick": "cwprogger" },
{ "nick": "cygal", "name": "Quentin Pradet" },
{ "nick": "Dak Lozar", "name": "Dave Loeser" },
{ "nick": "dalerank", "name": "Sergey Kushnirenko" },
{ "nick": "dan", "name": "Dan Strandberg" },
{ "nick": "DanCar", "name": "Daniel Cardenas" },
{ "nick": "danger89", "name": "Melroy van den Berg" },
{ "name": "Daniel Trevitz" },
{ "nick": "Dariost", "name": "Dario Ostuni" },
{ "nick": "Dave", "name": "David Protasowski" },
{ "name": "David Marshall" },
{ "nick": "dax", "name": "Dacian Fiordean" },
{ "nick": "deebee", "name": "Deepak Anthony" },
{ "nick": "Deiz" },
{ "nick": "Dietger", "name": "Dietger van Antwerpen" },
{ "nick": "DigitalSeraphim", "name": "Nick Owens" },
{ "nick": "dp304" },
{ "nick": "dpiquet", "name": "Damien Piquet" },
{ "nick": "dumbo" },
{ "nick": "Dunedan", "name": "Daniel Roschka" },
{ "nick": "dvangennip", "name": "Doménique" },
{ "nick": "DynamoFox" },
{ "nick": "Echelon9", "name": "Rhys Kidd" },
{ "nick": "echotangoecho" },
{ "nick": "eihrul", "name": "Lee Salzman" },
{ "nick": "elexis", "name": "Alexander Heinsius" },
{ "nick": "EmjeR", "name": "Matthijs de Rijk" },
{ "nick": "EMontana" },
{ "nick": "ericb" },
{ "nick": "evanssthomas", "name": "Evans Thomas" },
{ "nick": "Evulant", "name": "Alexander S." },
{ "nick": "fabio", "name": "Fabio Pedretti" },
{ "nick": "falsevision", "name": "Mahdi Khodadadifard" },
{ "nick": "fatherbushido", "name": "Nicolas Tisserand" },
{ "nick": "fcxSanya", "name": "Alexander Olkhovskiy" },
{ "nick": "FeXoR", "name": "Florian Finke" },
{ "nick": "Fire Giant", "name": "Malte Schwarzkopf" },
{ "name": "Fork AD" },
{ "nick": "fpre", "name": "Frederick Stallmeyer" },
{ "nick": "Freagarach" },
{ "nick": "freenity", "name": "Anton Galitch" },
{ "nick": "Gallaecio", "name": "Adrián Chaves" },
{ "nick": "gbish (aka Iny)", "name": "Grant Bishop" },
{ "nick": "Gee", "name": "Gustav Larsson" },
{ "nick": "Gentz", "name": "Hal Gentz" },
{ "nick": "gerbilOFdoom" },
{ "nick": "godlikeldh" },
{ "nick": "greybeard", "name": "Joe Cocovich" },
{ "nick": "grillaz" },
{ "nick": "Grugnas", "name": "Giuseppe Tranchese" },
{ "nick": "gudo" },
{ "nick": "Guuts", "name": "Matthew Guttag" },
{ "nick": "h20", "name": "Daniel Wilhelm" },
{ "nick": "Hannibal_Barca", "name": "Clive Juhász S." },
{ "nick": "Haommin" },
{ "nick": "happyconcepts", "name": "Ben Bird" },
{ "nick": "historic_bruno", "name": "Ben Brian" },
{ "nick": "hyiltiz", "name": "Hormet Yiltiz" },
{ "nick": "idanwin" },
{ "nick": "Imarok", "name": "J. S." },
{ "nick": "Inari" },
{ "nick": "infyquest", "name": "Vijay Kiran Kamuju" },
{ "nick": "irishninja", "name": "Brian Broll" },
{ "nick": "IronNerd", "name": "Matthew McMullan" },
{ "nick": "Itms", "name": "Nicolas Auvray" },
{ "nick": "Jaison", "name": "Marco tom Suden" },
{ "nick": "jammus", "name": "James Scott" },
{ "nick": "Jammyjamjamman", "name": "James Sherratt" },
{ "nick": "Janwas", "name": "Jan Wassenberg" },
{ "nick": "javiergodas", "name": "Javier Godas Vieitez" },
{ "nick": "JCWasmx86" },
{ "nick": "Jgwman" },
{ "nick": "JonBaer", "name": "Jon Baer" },
{ "nick": "Josh", "name": "Joshua J. Bakita" },
{ "nick": "joskar", "name": "Johnny Oskarsson" },
{ "nick": "jP_wanN", "name": "Jonas Platte" },
+ { "nick": "jprahman", "name": "Jason Rahman" },
{ "nick": "Jubalbarca", "name": "James Baillie" },
{ "nick": "JubJub", "name": "Sebastian Vetter" },
{ "nick": "jurgemaister" },
{ "nick": "kabzerek", "name": "Grzegorz Kabza" },
{ "nick": "Kai", "name": "Kai Chen" },
{ "name": "Kareem Ergawy" },
{ "nick": "kevmo", "name": "Kevin Caffrey" },
{ "nick": "kezz", "name": "Graeme Kerry" },
{ "nick": "kingadami", "name": "Adam Winsor" },
{ "nick": "kingbasil", "name": "Giannis Fafalios" },
{ "nick": "Krinkle", "name": "Timo Tijhof" },
{ "nick": "Kuba386", "name": "Jakub Kośmicki" },
{ "nick": "lafferjm", "name": "Justin Lafferty" },
{ "nick": "Langbart" },
{ "nick": "LeanderH", "name": "Leander Hemelhof" },
{ "nick": "leper", "name": "Georg Kilzer" },
{ "nick": "Link Mauve", "name": "Emmanuel Gil Peyrot" },
{ "nick": "LittleDev" },
{ "nick": "livingaftermidnight", "name": "Will Dull" },
{ "nick": "lonehawk", "name": "Vignesh Krishnan" },
{ "nick": "Louhike" },
{ "nick": "lsdh" },
{ "nick": "Ludovic", "name": "Ludovic Rousseau" },
{ "nick": "luiko", "name": "Luis Carlos Garcia Barajas" },
{ "nick": "m0l0t0ph", "name": "Christoph Gielisch" },
{ "nick": "madmax", "name": "Abhijit Nandy" },
{ "nick": "madpilot", "name": "Guido Falsi" },
{ "nick": "mammadori", "name": "Marco Amadori" },
{ "nick": "marder", "name": "Stefan R. F." },
{ "nick": "markcho" },
{ "nick": "MarkT", "name": "Mark Thompson" },
{ "nick": "Markus" },
{ "nick": "Mate-86", "name": "Mate Kovacs" },
{ "nick": "Matei", "name": "Matei Zaharia" },
{ "nick": "MatSharrow" },
{ "nick": "MattDoerksen", "name": "Matt Doerksen" },
{ "nick": "mattlott", "name": "Matt Lott" },
{ "nick": "maveric", "name": "Anton Protko" },
{ "nick": "Micnasty", "name": "Travis Gorkin" },
{ "name": "Mikołaj \"Bajter\" Korcz" },
{ "nick": "mimo" },
{ "nick": "mk12", "name": "Mitchell Kember" },
{ "nick": "mmayfield45", "name": "Michael Mayfield" },
{ "nick": "mmoanis", "name": "Mohamed Moanis" },
{ "nick": "Molotov", "name": "Dario Alvarez" },
{ "nick": "mpmoreti", "name": "Marcos Paulo Moreti" },
{ "nick": "mreiland", "name": "Michael Reiland" },
{ "nick": "myconid" },
{ "nick": "n1xc0d3r", "name": "Luis Guerrero" },
{ "nick": "nani", "name": "S. N." },
{ "nick": "nd3c3nt", "name": "Gavin Fowler" },
{ "nick": "nephele" },
{ "nick": "Nescio" },
{ "nick": "niektb", "name": "Niek ten Brinke" },
{ "nick": "nikagra", "name": "Mikita Hradovich" },
{ "nick": "njm" },
{ "nick": "NoMonkey", "name": "John Mena" },
{ "nick": "norsnor" },
{ "nick": "notpete", "name": "Rich Cross" },
{ "nick": "Nullus" },
{ "nick": "nwtour" },
{ "nick": "odoaker", "name": "Ágoston Sipos" },
{ "nick": "Offensive ePeen", "name": "Jared Ryan Bills" },
{ "nick": "Ols", "name": "Oliver Whiteman" },
{ "nick": "olsner", "name": "Simon Brenner" },
{ "nick": "OptimusShepard", "name": "Pirmin Stanglmeier" },
{ "nick": "otero" },
{ "nick": "Palaxin", "name": "David A. Freitag" },
{ "name": "Paul Withers" },
{ "nick": "paulobezerr", "name": "Paulo George Gomes Bezerra" },
{ "nick": "pcpa", "name": "Paulo Andrade" },
{ "nick": "Pendingchaos" },
{ "nick": "PeteVasi", "name": "Pete Vasiliauskas" },
{ "nick": "phosit" },
{ "nick": "pilino1234" },
{ "nick": "PingvinBetyar", "name": "Schronk Tamás" },
{ "nick": "plugwash", "name": "Peter Michael Green" },
{ "nick": "Polakrity" },
{ "nick": "Poya", "name": "Poya Manouchehri" },
{ "nick": "prefect", "name": "Nicolai Hähnle" },
{ "nick": "Prodigal Son" },
{ "nick": "pstumpf", "name": "Pascal Stumpf" },
{ "nick": "pszemsza", "name": "Przemek Szałaj" },
{ "nick": "pyrolink", "name": "Andrew Decker" },
{ "nick": "quantumstate", "name": "Jonathan Waller" },
{ "nick": "QuickShot", "name": "Walter Krawec" },
{ "nick": "quonter" },
{ "nick": "qwertz" },
{ "nick": "Radagast" },
{ "nick": "Raj", "name": "Raj Sharma" },
{ "nick": "ramtzok1", "name": "Ram" },
{ "nick": "rapidelectron", "name": "Christian Weihsbach" },
{ "nick": "r-a-sattarov", "name": "Ramil Sattarov" },
{ "nick": "RedFox", "name": "Jorma Rebane" },
{ "nick": "RefinedCode" },
{ "nick": "Riemer" },
{ "name": "Rolf Sievers" },
{ "nick": "s0600204", "name": "Matthew Norwood" },
{ "nick": "sacha_vrand", "name": "Sacha Vrand" },
{ "nick": "SafaAlfulaij" },
{ "name": "Samuel Guarnieri" },
{ "nick": "Samulis", "name": "Sam Gossner" },
{ "nick": "Sandarac" },
{ "nick": "sanderd17", "name": "Sander Deryckere" },
{ "nick": "sathyam", "name": "Sathyam Vellal" },
{ "nick": "sbirmi", "name": "Sharad Birmiwal" },
{ "nick": "sbte", "name": "Sven Baars" },
{ "nick": "scroogie", "name": "André Gemünd" },
{ "nick": "scythetwirler", "name": "Casey X." },
{ "nick": "sera", "name": "Ralph Sennhauser" },
{ "nick": "serveurix" },
{ "nick": "Shane", "name": "Shane Grant" },
{ "nick": "shh" },
{ "nick": "Silk", "name": "Josh Godsiff" },
{ "nick": "silure" },
{ "nick": "Simikolon", "name": "Yannick & Simon" },
{ "nick": "smiley", "name": "M. L." },
{ "nick": "Spahbod", "name": "Omid Davoodi" },
{ "nick": "Stan", "name": "Stanislas Dolcini" },
{ "nick": "Stefan" },
{ "nick": "StefanBruens", "name": "Stefan Brüns" },
{ "nick": "stilz", "name": "Sławomir Zborowski" },
{ "nick": "stwf", "name": "Steven Fuchs" },
{ "nick": "svott", "name": "Sven Ott" },
{ "nick": "t4nk004" },
{ "nick": "tau" },
{ "nick": "tbm", "name": "Martin Michlmayr" },
{ "nick": "Teiresias" },
{ "nick": "temple" },
{ "nick": "texane" },
{ "nick": "thamlett", "name": "Timothy Hamlett" },
{ "nick": "thedrunkyak", "name": "Dan Fuhr" },
{ "nick": "Tobbi" },
{ "nick": "Toonijn", "name": "Toon Baeyens" },
{ "nick": "TrinityDeath", "name": "Jethro Lu" },
{ "nick": "triumvir", "name": "Corin Schedler" },
{ "nick": "trompetin17", "name": "Juan Guillermo" },
{ "nick": "tpearson", "name": "Timothy Pearson" },
{ "nick": "user1", "name": "A. C." },
{ "nick": "usey11" },
{ "nick": "vincent_c", "name": "Vincent Cheng" },
{ "nick": "vinhig", "name": "Vincent Higginson" },
{ "nick": "vladislavbelov", "name": "Vladislav Belov" },
{ "nick": "voroskoi" },
{ "nick": "vts", "name": "Jeroen DR" },
{ "nick": "wacko", "name": "Andrew Spiering" },
{ "nick": "WhiteTreePaladin", "name": "Brian Ashley" },
{ "nick": "wowgetoffyourcellphone", "name": "Justus Avramenko" },
{ "nick": "wraitii", "name": "Lancelot de Ferrière le Vayer" },
{ "nick": "Xentelian", "name": "Mark Strawson" },
{ "nick": "Xienen", "name": "Dayle Flowers" },
{ "nick": "xone47", "name": "Brent Johnson" },
{ "nick": "xtizer", "name": "Matt Green" },
{ "nick": "yashi", "name": "Yasushi Shoji" },
{ "nick": "Ykkrosh", "name": "Philip Taylor" },
{ "nick": "Yves" },
{ "nick": "z0rg", "name": "Sébastien Maire" },
{ "nick": "Zeusthor", "name": "Jeffrey Tavares" },
{ "nick": "zoot" },
{ "nick": "zsol", "name": "Zsolt Dollenstein" },
{ "nick": "ztamas", "name": "Tamas Zolnai" },
{ "nick": "Zyi", "name": "Charles De Meulenaer" }
]
}
]
}
Index: ps/trunk/source/ps/Future.h
===================================================================
--- ps/trunk/source/ps/Future.h (revision 26915)
+++ ps/trunk/source/ps/Future.h (revision 26916)
@@ -1,326 +1,326 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 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 .
*/
#ifndef INCLUDED_FUTURE
#define INCLUDED_FUTURE
#include "ps/FutureForward.h"
#include
#include
#include
#include
#include
template
class PackagedTask;
namespace FutureSharedStateDetail
{
enum class Status
{
PENDING,
STARTED,
DONE,
CANCELED
};
template
class SharedStateResult
{
public:
void ResetResult()
{
if (m_HasResult)
m_Result.m_Result.~ResultType();
m_HasResult = false;
}
union Result
{
std::aligned_storage_t m_Bytes;
ResultType m_Result;
Result() : m_Bytes() {};
~Result() {};
};
// We don't use Result directly so the result doesn't have to be default constructible.
Result m_Result;
bool m_HasResult = false;
};
// Don't have m_Result for void ReturnType
template<>
class SharedStateResult
{
};
/**
* The shared state between futures and packaged state.
* Holds all relevant data.
*/
template
class SharedState : public SharedStateResult
{
static constexpr bool VoidResult = std::is_same_v;
public:
- SharedState(std::function&& func) : m_Func(func) {}
+ SharedState(std::function&& func) : m_Func(std::move(func)) {}
~SharedState()
{
// For safety, wait on started task completion, but not on pending ones (auto-cancelled).
if (!Cancel())
{
Wait();
Cancel();
}
if constexpr (!VoidResult)
SharedStateResult::ResetResult();
}
SharedState(const SharedState&) = delete;
SharedState(SharedState&&) = delete;
bool IsDoneOrCanceled() const
{
return m_Status == Status::DONE || m_Status == Status::CANCELED;
}
void Wait()
{
// Fast path: we're already done.
if (IsDoneOrCanceled())
return;
// Slow path: we aren't done when we run the above check. Lock and wait until we are.
std::unique_lock lock(m_Mutex);
m_ConditionVariable.wait(lock, [this]() -> bool { return IsDoneOrCanceled(); });
}
/**
* If the task is pending, cancel it: the status becomes CANCELED and if the task was completed, the result is destroyed.
* @return true if the task was indeed cancelled, false otherwise (the task is running or already done).
*/
bool Cancel()
{
Status expected = Status::PENDING;
bool cancelled = m_Status.compare_exchange_strong(expected, Status::CANCELED);
// If we're done, invalidate, if we're pending, atomically cancel, otherwise fail.
if (cancelled || m_Status == Status::DONE)
{
if (m_Status == Status::DONE)
m_Status = Status::CANCELED;
if constexpr (!VoidResult)
SharedStateResult::ResetResult();
m_ConditionVariable.notify_all();
return cancelled;
}
return false;
}
/**
* Move the result away from the shared state, mark the future invalid.
*/
template
std::enable_if_t, ResultType> GetResult()
{
// The caller must ensure that this is only called if we have a result.
ENSURE(SharedStateResult::m_HasResult);
m_Status = Status::CANCELED;
SharedStateResult::m_HasResult = false;
return std::move(SharedStateResult::m_Result.m_Result);
}
std::atomic m_Status = Status::PENDING;
std::mutex m_Mutex;
std::condition_variable m_ConditionVariable;
std::function m_Func;
};
} // namespace FutureSharedStateDetail
/**
* Corresponds to std::future.
* Unlike std::future, Future can request the cancellation of the task that would produce the result.
* This makes it more similar to Java's CancellableTask or C#'s Task.
* The name Future was kept over Task so it would be more familiar to C++ users,
* but this all should be revised once Concurrency TS wraps up.
*
* 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.
* TODO:
* - Handle exceptions.
*/
template
class Future
{
template
friend class PackagedTask;
static constexpr bool VoidResult = std::is_same_v;
using Status = FutureSharedStateDetail::Status;
using SharedState = FutureSharedStateDetail::SharedState;
public:
- Future() {};
+ Future() = default;
Future(const Future& o) = delete;
Future(Future&&) = default;
Future& operator=(Future&&) = default;
- ~Future() {}
+ ~Future() = default;
/**
* Make the future wait for the result of @a func.
*/
template
PackagedTask Wrap(T&& func);
/**
* Move the result out of the future, and invalidate the future.
* If the future is not complete, calls Wait().
* If the future is canceled, asserts.
*/
template
std::enable_if_t, ResultType> Get()
{
ENSURE(!!m_SharedState);
Wait();
if constexpr (VoidResult)
return;
else
{
ENSURE(m_SharedState->m_Status != Status::CANCELED);
// This mark the state invalid - can't call Get again.
return m_SharedState->GetResult();
}
}
/**
* @return true if the shared state is valid and has a result (i.e. Get can be called).
*/
bool IsReady() const
{
return !!m_SharedState && m_SharedState->m_Status == Status::DONE;
}
/**
* @return true if the future has a shared state and it's not been invalidated, ie. pending, started or done.
*/
bool Valid() const
{
return !!m_SharedState && m_SharedState->m_Status != Status::CANCELED;
}
void Wait()
{
if (Valid())
m_SharedState->Wait();
}
/**
* Cancels the task, waiting if the task is currently started.
* Use this function over Cancel() if you need to ensure determinism (i.e. in the simulation).
* @see Cancel.
*/
void CancelOrWait()
{
if (!Valid())
return;
if (!m_SharedState->Cancel())
m_SharedState->Wait();
m_SharedState.reset();
}
/**
* Cancels the task (without waiting).
* The result is always invalid, even if the task had completed before.
* Note that this cannot stop started tasks.
*/
void Cancel()
{
if (m_SharedState)
m_SharedState->Cancel();
m_SharedState.reset();
}
protected:
std::shared_ptr m_SharedState;
};
/**
* Corresponds somewhat to std::packaged_task.
* Like packaged_task, this holds a function acting as a promise.
* This type is mostly just the shared state and the call operator,
* handling the promise & continuation logic.
*/
template
class PackagedTask
{
static constexpr bool VoidResult = std::is_same_v;
public:
PackagedTask() = delete;
- PackagedTask(const std::shared_ptr::SharedState>& ss) : m_SharedState(ss) {}
+ PackagedTask(std::shared_ptr::SharedState> ss) : m_SharedState(std::move(ss)) {}
void operator()()
{
typename Future::Status expected = Future::Status::PENDING;
if (!m_SharedState->m_Status.compare_exchange_strong(expected, Future::Status::STARTED))
return;
if constexpr (VoidResult)
m_SharedState->m_Func();
else
{
// To avoid UB, explicitly placement-new the value.
new (&m_SharedState->m_Result) ResultType{std::move(m_SharedState->m_Func())};
m_SharedState->m_HasResult = true;
}
// Because we might have threads waiting on us, we need to make sure that they either:
// - don't wait on our condition variable
// - receive the notification when we're done.
// This requires locking the mutex (@see Wait).
{
std::lock_guard lock(m_SharedState->m_Mutex);
m_SharedState->m_Status = Future::Status::DONE;
}
m_SharedState->m_ConditionVariable.notify_all();
// We no longer need the shared state, drop it immediately.
m_SharedState.reset();
}
void Cancel()
{
m_SharedState->Cancel();
m_SharedState.reset();
}
protected:
std::shared_ptr::SharedState> m_SharedState;
};
template
template
PackagedTask Future::Wrap(T&& func)
{
static_assert(std::is_convertible_v, ResultType>, "The return type of the wrapped function cannot be converted to the type of the Future.");
m_SharedState = std::make_shared(std::move(func));
return PackagedTask(m_SharedState);
}
#endif // INCLUDED_FUTURE
Index: ps/trunk/source/ps/TaskManager.cpp
===================================================================
--- ps/trunk/source/ps/TaskManager.cpp (revision 26915)
+++ ps/trunk/source/ps/TaskManager.cpp (revision 26916)
@@ -1,311 +1,311 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 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 .
*/
#include "precompiled.h"
#include "TaskManager.h"
#include "lib/debug.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Threading.h"
#include "ps/ThreadUtil.h"
#include "ps/Profiler2.h"
#include
#include
#include
#include
#include
#include
namespace Threading
{
/**
* Minimum number of TaskManager workers.
*/
static constexpr size_t MIN_THREADS = 3;
/**
* Maximum number of TaskManager workers.
*/
static constexpr size_t MAX_THREADS = 32;
std::unique_ptr g_TaskManager;
class Thread;
using QueueItem = std::function;
/**
* Light wrapper around std::thread. Ensures Join has been called.
*/
class Thread
{
public:
Thread() = default;
Thread(const Thread&) = delete;
Thread(Thread&&) = delete;
template
void Start(T* object)
{
m_Thread = std::thread(HandleExceptions>::Wrapper, object);
}
template
static void DoStart(T* object);
protected:
~Thread()
{
ENSURE(!m_Thread.joinable());
}
std::thread m_Thread;
std::atomic m_Kill = false;
};
/**
* Worker thread: process the taskManager queues until killed.
*/
class WorkerThread : public Thread
{
public:
WorkerThread(TaskManager::Impl& taskManager);
~WorkerThread();
/**
* Wake the worker.
*/
void Wake();
protected:
void RunUntilDeath();
std::mutex m_Mutex;
std::condition_variable m_ConditionVariable;
TaskManager::Impl& m_TaskManager;
};
/**
* PImpl-ed implementation of the Task manager.
*
* The normal priority queue is processed first, the low priority only if there are no higher-priority tasks
*/
class TaskManager::Impl
{
friend class TaskManager;
friend class WorkerThread;
public:
Impl(TaskManager& backref);
~Impl()
{
ClearQueue();
m_Workers.clear();
}
/**
* 2-phase init to avoid having to think too hard about the order of class members.
*/
void SetupWorkers(size_t numberOfWorkers);
/**
* Push a task on the global queue.
* Takes ownership of @a task.
* May be called from any thread.
*/
void PushTask(std::function&& task, TaskPriority priority);
protected:
void ClearQueue();
template
bool PopTask(std::function& taskOut);
// Back reference (keep this first).
TaskManager& m_TaskManager;
std::atomic m_HasWork = false;
std::atomic m_HasLowPriorityWork = false;
std::mutex m_GlobalMutex;
std::mutex m_GlobalLowPriorityMutex;
std::deque m_GlobalQueue;
std::deque m_GlobalLowPriorityQueue;
// Ideally this would be a vector, since it does get iterated, but that requires movable types.
std::deque m_Workers;
// Round-robin counter for GetWorker.
mutable size_t m_RoundRobinIdx = 0;
};
TaskManager::TaskManager() : TaskManager(std::thread::hardware_concurrency() - 1)
{
}
TaskManager::TaskManager(size_t numberOfWorkers)
{
m = std::make_unique(*this);
numberOfWorkers = Clamp(numberOfWorkers, MIN_THREADS, MAX_THREADS);
m->SetupWorkers(numberOfWorkers);
}
-TaskManager::~TaskManager() {}
+TaskManager::~TaskManager() = default;
TaskManager::Impl::Impl(TaskManager& backref)
: m_TaskManager(backref)
{
}
void TaskManager::Impl::SetupWorkers(size_t numberOfWorkers)
{
for (size_t i = 0; i < numberOfWorkers; ++i)
m_Workers.emplace_back(*this);
}
void TaskManager::ClearQueue() { m->ClearQueue(); }
void TaskManager::Impl::ClearQueue()
{
{
std::lock_guard lock(m_GlobalMutex);
m_GlobalQueue.clear();
}
{
std::lock_guard lock(m_GlobalLowPriorityMutex);
m_GlobalLowPriorityQueue.clear();
}
}
size_t TaskManager::GetNumberOfWorkers() const
{
return m->m_Workers.size();
}
void TaskManager::DoPushTask(std::function&& task, TaskPriority priority)
{
m->PushTask(std::move(task), priority);
}
void TaskManager::Impl::PushTask(std::function&& task, TaskPriority priority)
{
std::mutex& mutex = priority == TaskPriority::NORMAL ? m_GlobalMutex : m_GlobalLowPriorityMutex;
std::deque& queue = priority == TaskPriority::NORMAL ? m_GlobalQueue : m_GlobalLowPriorityQueue;
std::atomic& hasWork = priority == TaskPriority::NORMAL ? m_HasWork : m_HasLowPriorityWork;
{
std::lock_guard lock(mutex);
queue.emplace_back(std::move(task));
hasWork = true;
}
for (WorkerThread& worker : m_Workers)
worker.Wake();
}
template
bool TaskManager::Impl::PopTask(std::function& taskOut)
{
std::mutex& mutex = Priority == TaskPriority::NORMAL ? m_GlobalMutex : m_GlobalLowPriorityMutex;
std::deque& queue = Priority == TaskPriority::NORMAL ? m_GlobalQueue : m_GlobalLowPriorityQueue;
std::atomic& hasWork = Priority == TaskPriority::NORMAL ? m_HasWork : m_HasLowPriorityWork;
// Particularly critical section since we're locking the global queue.
std::lock_guard globalLock(mutex);
if (!queue.empty())
{
taskOut = std::move(queue.front());
queue.pop_front();
hasWork = !queue.empty();
return true;
}
return false;
}
void TaskManager::Initialise()
{
if (!g_TaskManager)
g_TaskManager = std::make_unique();
}
TaskManager& TaskManager::Instance()
{
ENSURE(g_TaskManager);
return *g_TaskManager;
}
// Thread definition
WorkerThread::WorkerThread(TaskManager::Impl& taskManager)
: m_TaskManager(taskManager)
{
Start(this);
}
WorkerThread::~WorkerThread()
{
m_Kill = true;
m_ConditionVariable.notify_one();
if (m_Thread.joinable())
m_Thread.join();
}
void WorkerThread::Wake()
{
m_ConditionVariable.notify_one();
}
void WorkerThread::RunUntilDeath()
{
// The profiler does better if the names are unique.
static std::atomic n = 0;
std::string name = "Task Mgr #" + std::to_string(n++);
debug_SetThreadName(name.c_str());
g_Profiler2.RegisterCurrentThread(name);
std::function task;
bool hasTask = false;
std::unique_lock lock(m_Mutex, std::defer_lock);
while (!m_Kill)
{
lock.lock();
m_ConditionVariable.wait(lock, [this](){
return m_Kill || m_TaskManager.m_HasWork || m_TaskManager.m_HasLowPriorityWork;
});
lock.unlock();
if (m_Kill)
break;
// Fetch work from the global queues.
hasTask = m_TaskManager.PopTask(task);
if (!hasTask)
hasTask = m_TaskManager.PopTask(task);
if (hasTask)
task();
}
}
// Defined here - needs access to derived types.
template
void Thread::DoStart(T* object)
{
std::invoke(callable, object);
}
} // namespace Threading
Index: ps/trunk/source/ps/TaskManager.h
===================================================================
--- ps/trunk/source/ps/TaskManager.h (revision 26915)
+++ ps/trunk/source/ps/TaskManager.h (revision 26916)
@@ -1,84 +1,84 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 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 .
*/
#ifndef INCLUDED_THREADING_TASKMANAGER
#define INCLUDED_THREADING_TASKMANAGER
#include "ps/Future.h"
#include
#include
namespace Threading
{
enum class TaskPriority
{
NORMAL,
LOW
};
/**
* The task manager creates all worker threads on initialisation,
* and manages the task queues.
* See implementation for additional comments.
*/
class TaskManager
{
friend class WorkerThread;
public:
TaskManager();
~TaskManager();
TaskManager(const TaskManager&) = delete;
TaskManager(TaskManager&&) = delete;
TaskManager& operator=(const TaskManager&) = delete;
TaskManager& operator=(TaskManager&&) = delete;
static void Initialise();
static TaskManager& Instance();
/**
* Clears all tasks from the queue. This blocks on started tasks.
*/
void ClearQueue();
/**
* @return the number of threaded workers.
*/
size_t GetNumberOfWorkers() const;
/**
* Push a task to be executed.
*/
template
Future> PushTask(T&& func, TaskPriority priority = TaskPriority::NORMAL)
{
Future> ret;
- DoPushTask(std::move(ret.Wrap(std::move(func))), priority);
+ DoPushTask(ret.Wrap(std::move(func)), priority);
return ret;
}
private:
TaskManager(size_t numberOfWorkers);
void DoPushTask(std::function&& task, TaskPriority priority);
class Impl;
std::unique_ptr m;
};
} // namespace Threading
#endif // INCLUDED_THREADING_TASKMANAGER