Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp
===================================================================
--- ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 27063)
+++ ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 27064)
@@ -1,249 +1,257 @@
/* 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 "lib/svn_revision.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/posix/posix_utsname.h"
#include "lib/sysdep/cpu.h"
#include "lib/sysdep/numa.h"
#include "lib/sysdep/os_cpu.h"
#if CONFIG2_AUDIO
#include "soundmanager/SoundManager.h"
#endif
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "ps/scripting/JSInterface_ConfigDB.h"
#include "ps/scripting/JSInterface_Debug.h"
#include "ps/UserReport.h"
#include "ps/VideoMode.h"
#include "renderer/backend/IDevice.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/JSON.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptInterface.h"
// FreeType headers might have an include order.
#include
#include
#if OS_LINUX
#include
#endif
#include
#include
+#include
static void ReportSDL(const ScriptRequest& rq, JS::HandleValue settings);
static void ReportFreeType(const ScriptRequest& rq, JS::HandleValue settings);
void SetDisableAudio(bool disabled)
{
g_DisableAudio = disabled;
}
void RunHardwareDetection()
{
TIMER(L"RunHardwareDetection");
ScriptInterface scriptInterface("Engine", "HWDetect", g_ScriptContext);
ScriptRequest rq(scriptInterface);
JSI_Debug::RegisterScriptFunctions(scriptInterface); // Engine.DisplayErrorDialog
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
ScriptFunction::Register(rq, "SetDisableAudio");
// Load the detection script:
const wchar_t* scriptName = L"hwdetect/hwdetect.js";
CVFSFile file;
if (file.Load(g_VFS, scriptName) != PSRETURN_OK)
{
LOGERROR("Failed to load hardware detection script");
return;
}
std::string code = file.DecodeUTF8(); // assume it's UTF-8
scriptInterface.LoadScript(scriptName, code);
// Collect all the settings we'll pass to the script:
// (We'll use this same data for the opt-in online reporting system, so it
// includes some fields that aren't directly useful for the hwdetect script)
JS::RootedValue settings(rq.cx);
Script::CreateObject(rq, &settings);
Script::SetProperty(rq, settings, "os_unix", OS_UNIX);
Script::SetProperty(rq, settings, "os_bsd", OS_BSD);
Script::SetProperty(rq, settings, "os_linux", OS_LINUX);
Script::SetProperty(rq, settings, "os_android", OS_ANDROID);
Script::SetProperty(rq, settings, "os_macosx", OS_MACOSX);
Script::SetProperty(rq, settings, "os_win", OS_WIN);
Script::SetProperty(rq, settings, "arch_ia32", ARCH_IA32);
Script::SetProperty(rq, settings, "arch_amd64", ARCH_AMD64);
Script::SetProperty(rq, settings, "arch_arm", ARCH_ARM);
Script::SetProperty(rq, settings, "arch_aarch64", ARCH_AARCH64);
Script::SetProperty(rq, settings, "arch_e2k", ARCH_E2K);
Script::SetProperty(rq, settings, "arch_ppc64", ARCH_PPC64);
#ifdef NDEBUG
Script::SetProperty(rq, settings, "build_debug", 0);
#else
Script::SetProperty(rq, settings, "build_debug", 1);
#endif
Script::SetProperty(rq, settings, "build_opengles", CONFIG2_GLES);
Script::SetProperty(rq, settings, "build_datetime", std::string(__DATE__ " " __TIME__));
Script::SetProperty(rq, settings, "build_revision", std::wstring(svn_revision));
Script::SetProperty(rq, settings, "build_msc", (int)MSC_VERSION);
Script::SetProperty(rq, settings, "build_icc", (int)ICC_VERSION);
Script::SetProperty(rq, settings, "build_gcc", (int)GCC_VERSION);
Script::SetProperty(rq, settings, "build_clang", (int)CLANG_VERSION);
Script::SetProperty(rq, settings, "gfx_card", g_VideoMode.GetBackendDevice()->GetName());
Script::SetProperty(rq, settings, "gfx_drv_ver", g_VideoMode.GetBackendDevice()->GetDriverInformation());
#if CONFIG2_AUDIO
if (g_SoundManager)
{
Script::SetProperty(rq, settings, "snd_card", g_SoundManager->GetSoundCardNames());
Script::SetProperty(rq, settings, "snd_drv_ver", g_SoundManager->GetOpenALVersion());
}
#endif
ReportSDL(rq, settings);
ReportFreeType(rq, settings);
JS::RootedValue backendDeviceSettings(rq.cx);
Script::CreateObject(rq, &backendDeviceSettings);
g_VideoMode.GetBackendDevice()->Report(rq, backendDeviceSettings);
Script::SetProperty(rq, settings, "renderer_backend", backendDeviceSettings);
Script::SetProperty(rq, settings, "video_desktop_xres", g_VideoMode.GetDesktopXRes());
Script::SetProperty(rq, settings, "video_desktop_yres", g_VideoMode.GetDesktopYRes());
Script::SetProperty(rq, settings, "video_desktop_bpp", g_VideoMode.GetDesktopBPP());
Script::SetProperty(rq, settings, "video_desktop_freq", g_VideoMode.GetDesktopFreq());
struct utsname un;
uname(&un);
Script::SetProperty(rq, settings, "uname_sysname", std::string(un.sysname));
Script::SetProperty(rq, settings, "uname_release", std::string(un.release));
Script::SetProperty(rq, settings, "uname_version", std::string(un.version));
Script::SetProperty(rq, settings, "uname_machine", std::string(un.machine));
#if OS_LINUX
{
std::ifstream ifs("/etc/lsb-release");
if (ifs.good())
{
std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator());
Script::SetProperty(rq, settings, "linux_release", str);
}
}
#endif
Script::SetProperty(rq, settings, "cpu_identifier", std::string(cpu_IdentifierString()));
Script::SetProperty(rq, settings, "cpu_frequency", os_cpu_ClockFrequency());
Script::SetProperty(rq, settings, "cpu_pagesize", (u32)os_cpu_PageSize());
Script::SetProperty(rq, settings, "cpu_largepagesize", (u32)os_cpu_LargePageSize());
Script::SetProperty(rq, settings, "cpu_numprocs", (u32)os_cpu_NumProcessors());
Script::SetProperty(rq, settings, "numa_numnodes", (u32)numa_NumNodes());
Script::SetProperty(rq, settings, "numa_factor", numa_Factor());
Script::SetProperty(rq, settings, "numa_interleaved", numa_IsMemoryInterleaved());
Script::SetProperty(rq, settings, "ram_total", (u32)os_cpu_MemorySize());
Script::SetProperty(rq, settings, "ram_total_os", (u32)os_cpu_QueryMemorySize());
#if ARCH_X86_X64
Script::SetProperty(rq, settings, "x86_vendor", (u32)x86_x64::Vendor());
Script::SetProperty(rq, settings, "x86_model", (u32)x86_x64::Model());
Script::SetProperty(rq, settings, "x86_family", (u32)x86_x64::Family());
u32 caps0, caps1, caps2, caps3;
x86_x64::GetCapBits(&caps0, &caps1, &caps2, &caps3);
Script::SetProperty(rq, settings, "x86_caps[0]", caps0);
Script::SetProperty(rq, settings, "x86_caps[1]", caps1);
Script::SetProperty(rq, settings, "x86_caps[2]", caps2);
Script::SetProperty(rq, settings, "x86_caps[3]", caps3);
#endif
Script::SetProperty(rq, settings, "timer_resolution", timer_Resolution());
+ Script::SetProperty(rq, settings, "hardware_concurrency", std::thread::hardware_concurrency());
+
// The version should be increased for every meaningful change.
- const int reportVersion = 19;
+ const int reportVersion = 20;
// Send the same data to the reporting system
g_UserReporter.SubmitReport(
"hwdetect",
reportVersion,
Script::StringifyJSON(rq, &settings, false),
Script::StringifyJSON(rq, &settings, true));
// Run the detection script:
JS::RootedValue global(rq.cx, rq.globalValue());
ScriptFunction::CallVoid(rq, global, "RunHardwareDetection", settings);
}
static void ReportSDL(const ScriptRequest& rq, JS::HandleValue settings)
{
SDL_version build, runtime;
SDL_VERSION(&build);
char version[16];
snprintf(version, ARRAY_SIZE(version), "%d.%d.%d", build.major, build.minor, build.patch);
Script::SetProperty(rq, settings, "sdl_build_version", version);
SDL_GetVersion(&runtime);
snprintf(version, ARRAY_SIZE(version), "%d.%d.%d", runtime.major, runtime.minor, runtime.patch);
Script::SetProperty(rq, settings, "sdl_runtime_version", version);
// This is null in atlas (and further the call triggers an assertion).
const char* backend = g_VideoMode.GetWindow() ? GetSDLSubsystem(g_VideoMode.GetWindow()) : "none";
Script::SetProperty(rq, settings, "sdl_video_backend", backend ? backend : "unknown");
+
+ Script::SetProperty(rq, settings, "sdl_display_count", SDL_GetNumVideoDisplays());
+
+ Script::SetProperty(rq, settings, "sdl_cpu_count", SDL_GetCPUCount());
+ Script::SetProperty(rq, settings, "sdl_system_ram", SDL_GetSystemRAM());
}
static void ReportFreeType(const ScriptRequest& rq, JS::HandleValue settings)
{
FT_Library FTLibrary;
std::string FTSupport = "unsupported";
if (!FT_Init_FreeType(&FTLibrary))
{
FT_Int major, minor, patch;
FT_Library_Version(FTLibrary, &major, &minor, &patch);
FT_Done_FreeType(FTLibrary);
std::stringstream version;
version << major << "." << minor << "." << patch;
FTSupport = version.str();
}
else
{
FTSupport = "cantload";
}
Script::SetProperty(rq, settings, "freetype", FTSupport);
}
Index: ps/trunk/source/ps/TaskManager.cpp
===================================================================
--- ps/trunk/source/ps/TaskManager.cpp (revision 27063)
+++ ps/trunk/source/ps/TaskManager.cpp (revision 27064)
@@ -1,311 +1,322 @@
/* 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
{
+
+namespace
+{
/**
* Minimum number of TaskManager workers.
*/
-static constexpr size_t MIN_THREADS = 3;
+constexpr size_t MIN_WORKERS = 3;
/**
* Maximum number of TaskManager workers.
*/
-static constexpr size_t MAX_THREADS = 32;
+constexpr size_t MAX_WORKERS = 32;
+
+size_t GetDefaultNumberOfWorkers()
+{
+ const size_t hardware_concurrency = std::thread::hardware_concurrency();
+ return hardware_concurrency ? Clamp(hardware_concurrency - 1, MIN_WORKERS, MAX_WORKERS) : MIN_WORKERS;
+}
+
+} // anonymous namespace
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() : TaskManager(GetDefaultNumberOfWorkers())
{
}
TaskManager::TaskManager(size_t numberOfWorkers)
{
m = std::make_unique(*this);
- numberOfWorkers = Clamp(numberOfWorkers, MIN_THREADS, MAX_THREADS);
+ numberOfWorkers = Clamp(numberOfWorkers, MIN_WORKERS, MAX_WORKERS);
m->SetupWorkers(numberOfWorkers);
}
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