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