linux WIP
This commit is contained in:
parent
fa858997d4
commit
a6413f7d57
21 changed files with 258 additions and 446 deletions
|
@ -22,7 +22,6 @@
|
|||
#include "engine/job_system.h"
|
||||
#include "engine/log.h"
|
||||
#include "engine/lua_wrapper.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/os.h"
|
||||
#include "engine/path_utils.h"
|
||||
#include "engine/plugin_manager.h"
|
||||
|
@ -187,7 +186,7 @@ public:
|
|||
, m_events(m_allocator)
|
||||
, m_windows(m_allocator)
|
||||
{
|
||||
if (!JobSystem::init(MT::getCPUsCount(), m_allocator)) {
|
||||
if (!JobSystem::init(OS::getCPUsCount(), m_allocator)) {
|
||||
logError("Engine") << "Failed to initialize job system.";
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +298,7 @@ public:
|
|||
|
||||
if (frame_time < 1 / wanted_fps) {
|
||||
PROFILE_BLOCK("sleep");
|
||||
MT::sleep(u32(1000 / wanted_fps - frame_time * 1000));
|
||||
OS::sleep(u32(1000 / wanted_fps - frame_time * 1000));
|
||||
}
|
||||
m_inactive_fps_timer.tick();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "engine/math.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/mt/task.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/profiler.h"
|
||||
|
||||
|
||||
|
@ -70,11 +69,10 @@ struct System
|
|||
, m_free_queue(allocator)
|
||||
, m_free_fibers(allocator)
|
||||
, m_backup_workers(allocator)
|
||||
, m_outside_job_semaphore(0, 1)
|
||||
{
|
||||
m_signals_pool.resize(4096);
|
||||
m_free_queue.resize(4096);
|
||||
m_event_outside_job.trigger();
|
||||
m_work_signal.reset();
|
||||
for(int i = 0; i < 4096; ++i) {
|
||||
m_free_queue[i] = i;
|
||||
m_signals_pool[i].sibling = JobSystem::INVALID_HANDLE;
|
||||
|
@ -85,8 +83,7 @@ struct System
|
|||
|
||||
MT::CriticalSection m_sync;
|
||||
MT::CriticalSection m_job_queue_sync;
|
||||
MT::Event m_event_outside_job;
|
||||
MT::Event m_work_signal;
|
||||
MT::Semaphore m_outside_job_semaphore;
|
||||
Array<WorkerTask*> m_workers;
|
||||
Array<WorkerTask*> m_backup_workers;
|
||||
Array<Job> m_job_queue;
|
||||
|
@ -119,8 +116,6 @@ struct WorkerTask : MT::Task
|
|||
, m_job_queue(system.m_allocator)
|
||||
, m_ready_fibers(system.m_allocator)
|
||||
{
|
||||
m_enabled.reset();
|
||||
m_work_signal.reset();
|
||||
}
|
||||
|
||||
|
||||
|
@ -159,8 +154,6 @@ struct WorkerTask : MT::Task
|
|||
u8 m_worker_index;
|
||||
bool m_is_enabled = false;
|
||||
bool m_is_backup = false;
|
||||
MT::Event m_enabled;
|
||||
MT::Event m_work_signal;
|
||||
};
|
||||
|
||||
|
||||
|
@ -184,11 +177,13 @@ static void pushJob(const Job& job)
|
|||
if (job.worker_index != ANY_WORKER) {
|
||||
WorkerTask* worker = g_system->m_workers[job.worker_index % g_system->m_workers.size()];
|
||||
worker->m_job_queue.push(job);
|
||||
worker->m_work_signal.trigger();
|
||||
worker->wakeup();
|
||||
return;
|
||||
}
|
||||
g_system->m_job_queue.push(job);
|
||||
g_system->m_work_signal.trigger();
|
||||
for (WorkerTask* worker : g_system->m_workers) {
|
||||
worker->wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -285,12 +280,6 @@ void enableBackupWorker(bool enable)
|
|||
for (WorkerTask* task : g_system->m_backup_workers) {
|
||||
if (task->m_is_enabled != enable) {
|
||||
task->m_is_enabled = enable;
|
||||
if (enable) {
|
||||
task->m_enabled.trigger();
|
||||
}
|
||||
else {
|
||||
task->m_enabled.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -301,7 +290,6 @@ void enableBackupWorker(bool enable)
|
|||
g_system->m_backup_workers.push(task);
|
||||
task->m_is_enabled = true;
|
||||
task->m_is_backup = true;
|
||||
task->m_enabled.trigger();
|
||||
}
|
||||
else {
|
||||
logError("Engine") << "Job system backup worker failed to initialize.";
|
||||
|
@ -358,38 +346,43 @@ void runEx(void* data, void(*task)(void*), SignalHandle* on_finished, SignalHand
|
|||
WorkerTask* worker = getWorker();
|
||||
while (!worker->m_finished) {
|
||||
if (worker->m_is_backup) {
|
||||
if (!worker->m_enabled.poll()) {
|
||||
MT::CriticalSectionLock guard(g_system->m_sync);
|
||||
while (!worker->m_is_enabled) {
|
||||
PROFILE_BLOCK("disabled");
|
||||
Profiler::blockColor(0xff, 0, 0xff);
|
||||
worker->m_enabled.wait();
|
||||
worker->sleep(g_system->m_sync);
|
||||
}
|
||||
}
|
||||
|
||||
FiberDecl* fiber = nullptr;
|
||||
Job job;
|
||||
{
|
||||
while (!worker->m_finished) {
|
||||
MT::CriticalSectionLock lock(g_system->m_job_queue_sync);
|
||||
|
||||
if (!worker->m_ready_fibers.empty()) {
|
||||
fiber = worker->m_ready_fibers.back();
|
||||
worker->m_ready_fibers.pop();
|
||||
if (worker->m_ready_fibers.empty()) worker->m_work_signal.reset();
|
||||
break;
|
||||
}
|
||||
else if (!worker->m_job_queue.empty()) {
|
||||
if (!worker->m_job_queue.empty()) {
|
||||
job = worker->m_job_queue.back();
|
||||
worker->m_job_queue.pop();
|
||||
if (worker->m_job_queue.empty()) worker->m_work_signal.reset();
|
||||
break;
|
||||
}
|
||||
else if (!g_system->m_ready_fibers.empty()) {
|
||||
if (!g_system->m_ready_fibers.empty()) {
|
||||
fiber = g_system->m_ready_fibers.back();
|
||||
g_system->m_ready_fibers.pop();
|
||||
if (g_system->m_ready_fibers.empty()) g_system->m_work_signal.reset();
|
||||
break;
|
||||
}
|
||||
else if(!g_system->m_job_queue.empty()) {
|
||||
if(!g_system->m_job_queue.empty()) {
|
||||
job = g_system->m_job_queue.back();
|
||||
g_system->m_job_queue.pop();
|
||||
if (g_system->m_job_queue.empty()) g_system->m_work_signal.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
PROFILE_BLOCK("sleeping");
|
||||
Profiler::blockColor(0xff, 0, 0xff);
|
||||
worker->sleep(g_system->m_job_queue_sync);
|
||||
}
|
||||
|
||||
if (fiber) {
|
||||
|
@ -406,10 +399,8 @@ void runEx(void* data, void(*task)(void*), SignalHandle* on_finished, SignalHand
|
|||
Profiler::blockColor(0, 0, 0xff);
|
||||
worker = getWorker();
|
||||
worker->m_current_fiber = this_fiber;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (job.task) {
|
||||
else {
|
||||
Profiler::endBlock();
|
||||
Profiler::beginBlock("job");
|
||||
if (isValid(job.dec_on_finish) || isValid(job.precondition)) {
|
||||
|
@ -426,12 +417,6 @@ void runEx(void* data, void(*task)(void*), SignalHandle* on_finished, SignalHand
|
|||
Profiler::beginBlock("job management");
|
||||
Profiler::blockColor(0, 0, 0xff);
|
||||
}
|
||||
else
|
||||
{
|
||||
PROFILE_BLOCK("idle");
|
||||
Profiler::blockColor(0xff, 0, 0xff);
|
||||
MT::Event::waitMultiple(g_system->m_work_signal, worker->m_work_signal, 1);
|
||||
}
|
||||
}
|
||||
Profiler::endBlock();
|
||||
Fiber::switchTo(&getWorker()->m_current_fiber->fiber, getWorker()->m_primary_fiber);
|
||||
|
@ -443,7 +428,6 @@ bool init(u8 workers_count, IAllocator& allocator)
|
|||
ASSERT(!g_system);
|
||||
|
||||
g_system = LUMIX_NEW(allocator, System)(allocator);
|
||||
g_system->m_work_signal.reset();
|
||||
|
||||
g_system->m_free_fibers.reserve(lengthOf(g_system->m_fiber_pool));
|
||||
for (FiberDecl& fiber : g_system->m_fiber_pool) {
|
||||
|
@ -461,7 +445,6 @@ bool init(u8 workers_count, IAllocator& allocator)
|
|||
WorkerTask* task = LUMIX_NEW(allocator, WorkerTask)(*g_system, i < 64 ? u64(1) << i : 0);
|
||||
if (task->create("Worker", false)) {
|
||||
task->m_is_enabled = true;
|
||||
task->m_enabled.trigger();
|
||||
g_system->m_workers.push(task);
|
||||
task->setAffinityMask((u64)1 << i);
|
||||
}
|
||||
|
@ -493,23 +476,22 @@ void shutdown()
|
|||
WorkerTask* wt = (WorkerTask*)task;
|
||||
wt->m_finished = true;
|
||||
}
|
||||
for (MT::Task* task : g_system->m_backup_workers)
|
||||
{
|
||||
for (MT::Task* task : g_system->m_backup_workers) {
|
||||
WorkerTask* wt = (WorkerTask*)task;
|
||||
wt->m_finished = true;
|
||||
wt->m_enabled.trigger();
|
||||
wt->wakeup();
|
||||
}
|
||||
|
||||
for (MT::Task* task : g_system->m_backup_workers)
|
||||
for (WorkerTask* task : g_system->m_backup_workers)
|
||||
{
|
||||
while (!task->isFinished()) g_system->m_work_signal.trigger();
|
||||
while (!task->isFinished()) task->wakeup();
|
||||
task->destroy();
|
||||
LUMIX_DELETE(allocator, task);
|
||||
}
|
||||
|
||||
for (MT::Task* task : g_system->m_workers)
|
||||
for (WorkerTask* task : g_system->m_workers)
|
||||
{
|
||||
while (!task->isFinished()) g_system->m_work_signal.trigger();
|
||||
while (!task->isFinished()) task->wakeup();
|
||||
task->destroy();
|
||||
LUMIX_DELETE(allocator, task);
|
||||
}
|
||||
|
@ -543,12 +525,14 @@ void wait(SignalHandle handle)
|
|||
FiberDecl* fiber = (FiberDecl*)data;
|
||||
if (fiber->current_job.worker_index == ANY_WORKER) {
|
||||
g_system->m_ready_fibers.push(fiber);
|
||||
g_system->m_work_signal.trigger();
|
||||
for (WorkerTask* worker : g_system->m_workers) {
|
||||
worker->wakeup();
|
||||
}
|
||||
}
|
||||
else {
|
||||
WorkerTask* worker = g_system->m_workers[fiber->current_job.worker_index % g_system->m_workers.size()];
|
||||
worker->m_ready_fibers.push(fiber);
|
||||
worker->m_work_signal.trigger();
|
||||
worker->wakeup();
|
||||
}
|
||||
}, handle, false, nullptr, 0);
|
||||
|
||||
|
@ -576,18 +560,16 @@ void wait(SignalHandle handle)
|
|||
PROFILE_BLOCK("not a job waiting");
|
||||
Profiler::blockColor(0xff, 0, 0);
|
||||
|
||||
g_system->m_event_outside_job.reset();
|
||||
|
||||
static bool singleton = true;
|
||||
ASSERT(singleton); // only one external thread supported
|
||||
singleton = false;
|
||||
runInternal(nullptr, [](void* data) {
|
||||
g_system->m_event_outside_job.trigger();
|
||||
g_system->m_outside_job_semaphore.signal();
|
||||
}, handle, false, nullptr, 0);
|
||||
|
||||
g_system->m_sync.exit();
|
||||
|
||||
MT::yield();
|
||||
while (!isSignalZero(handle, true)) {
|
||||
g_system->m_event_outside_job.waitTimeout(100);
|
||||
}
|
||||
g_system->m_outside_job_semaphore.wait();
|
||||
singleton = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -192,6 +192,9 @@ OutputFile& OutputFile::operator <<(float value)
|
|||
return *this;
|
||||
}
|
||||
|
||||
u32 getCPUsCount() { return sysconf(_SC_NPROCESSORS_ONLN); }
|
||||
void sleep(u32 milliseconds) { if (milliseconds) usleep(useconds_t(milliseconds * 1000)); }
|
||||
ThreadID getCurrentThreadID() { return pthread_self(); }
|
||||
|
||||
void logVersion() {
|
||||
struct utsname tmp;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include "engine/crt.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/mt/atomic.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/profiler.h"
|
||||
#include "engine/string.h"
|
||||
#include <errno.h>
|
||||
|
@ -15,6 +14,25 @@ namespace Lumix
|
|||
namespace MT
|
||||
{
|
||||
|
||||
ConditionVariable::ConditionVariable() {
|
||||
const int res = pthread_cond_init(&cv, nullptr);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
ConditionVariable::~ConditionVariable() {
|
||||
const int res = pthread_cond_destroy(&cv);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
void ConditionVariable::sleep(CriticalSection& cs) {
|
||||
const int res = pthread_cond_wait(&cv, &cs.mutex);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
void ConditionVariable::wakeup() {
|
||||
const int res = pthread_cond_signal(&cv);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
Semaphore::Semaphore(int init_count, int max_count)
|
||||
{
|
||||
|
@ -61,82 +79,6 @@ void Semaphore::wait()
|
|||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
bool Semaphore::poll()
|
||||
{
|
||||
int res = pthread_mutex_lock(&m_id.mutex);
|
||||
ASSERT(res == 0);
|
||||
|
||||
bool ret = false;
|
||||
if(m_id.count > 0)
|
||||
{
|
||||
--m_id.count;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
res = pthread_mutex_unlock(&m_id.mutex);
|
||||
ASSERT(res == 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Event::Event()
|
||||
{
|
||||
m_id = eventfd(0, EFD_NONBLOCK);
|
||||
}
|
||||
|
||||
Event::~Event()
|
||||
{
|
||||
close(m_id);
|
||||
}
|
||||
|
||||
void Event::reset()
|
||||
{
|
||||
u64 v;
|
||||
read(m_id, &v, sizeof(v)); // reset to 0, nonblocking
|
||||
}
|
||||
|
||||
void Event::trigger()
|
||||
{
|
||||
const u64 v = 1;
|
||||
const ssize_t res = write(m_id, &v, sizeof(v));
|
||||
ASSERT(res == sizeof(v));
|
||||
}
|
||||
|
||||
void Event::waitMultiple(Event& event0, Event& event1, u32 timeout_ms)
|
||||
{
|
||||
pollfd fds[2];
|
||||
fds[0].fd = event0.m_id;
|
||||
fds[1].fd = event1.m_id;
|
||||
fds[0].events = POLLIN;
|
||||
fds[1].events = POLLIN;
|
||||
::poll(fds, 2, timeout_ms);
|
||||
}
|
||||
|
||||
void Event::waitTimeout(u32 timeout_ms)
|
||||
{
|
||||
pollfd pfd;
|
||||
pfd.fd = m_id;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, timeout_ms);
|
||||
}
|
||||
|
||||
void Event::wait()
|
||||
{
|
||||
pollfd pfd;
|
||||
pfd.fd = m_id;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, -1);
|
||||
}
|
||||
|
||||
bool Event::poll()
|
||||
{
|
||||
pollfd pfd;
|
||||
pfd.fd = m_id;
|
||||
pfd.events = POLLIN;
|
||||
const int res = ::poll(&pfd, 1, 0);
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
CriticalSection::CriticalSection()
|
||||
{
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
#include "engine/allocator.h"
|
||||
#include "engine/lumix.h"
|
||||
#include "engine/mt/task.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/profiler.h"
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
namespace Lumix
|
||||
{
|
||||
namespace MT
|
||||
{
|
||||
|
||||
struct TaskImpl
|
||||
{
|
||||
IAllocator& allocator;
|
||||
bool force_exit;
|
||||
bool exited;
|
||||
bool is_running;
|
||||
pthread_t handle;
|
||||
const char* thread_name;
|
||||
u64 affinity_mask;
|
||||
Task* owner;
|
||||
};
|
||||
|
||||
static void* threadFunction(void* ptr)
|
||||
{
|
||||
struct TaskImpl* impl = reinterpret_cast<TaskImpl*>(ptr);
|
||||
setThreadName(getCurrentThreadID(), impl->thread_name);
|
||||
Profiler::setThreadName(impl->thread_name);
|
||||
u32 ret = 0xffffFFFF;
|
||||
if (!impl->force_exit) ret = impl->owner->task();
|
||||
impl->exited = true;
|
||||
impl->is_running = false;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Task::Task(IAllocator& allocator)
|
||||
{
|
||||
auto impl = LUMIX_NEW(allocator, TaskImpl) {allocator};
|
||||
|
||||
impl->is_running = false;
|
||||
impl->force_exit = false;
|
||||
impl->exited = false;
|
||||
impl->thread_name = "";
|
||||
impl->owner = this;
|
||||
impl->affinity_mask = getThreadAffinityMask();
|
||||
|
||||
m_implementation = impl;
|
||||
}
|
||||
|
||||
Task::~Task()
|
||||
{
|
||||
LUMIX_DELETE(m_implementation->allocator, m_implementation);
|
||||
}
|
||||
|
||||
bool Task::create(const char* name, bool is_extended)
|
||||
{
|
||||
pthread_attr_t attr;
|
||||
int res = pthread_attr_init(&attr);
|
||||
ASSERT(res == 0);
|
||||
if (res != 0) return false;
|
||||
res = pthread_create(&m_implementation->handle, &attr, threadFunction, m_implementation);
|
||||
ASSERT(res == 0);
|
||||
if (res != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Task::destroy()
|
||||
{
|
||||
return pthread_join(m_implementation->handle, nullptr) == 0;
|
||||
}
|
||||
|
||||
void Task::setAffinityMask(u64 affinity_mask)
|
||||
{
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
if (affinity_mask & ((u64)1 << i))
|
||||
{
|
||||
CPU_SET(i, &set);
|
||||
}
|
||||
}
|
||||
m_implementation->affinity_mask = affinity_mask;
|
||||
pthread_setaffinity_np(m_implementation->handle, sizeof(set), &set);
|
||||
}
|
||||
|
||||
bool Task::isRunning() const
|
||||
{
|
||||
return m_implementation->is_running;
|
||||
}
|
||||
|
||||
bool Task::isFinished() const
|
||||
{
|
||||
return m_implementation->exited;
|
||||
}
|
||||
|
||||
IAllocator& Task::getAllocator()
|
||||
{
|
||||
return m_implementation->allocator;
|
||||
}
|
||||
|
||||
} // namespace MT
|
||||
} // namespace Lumix
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
#include "engine/allocator.h"
|
||||
#include "engine/lumix.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/mt/task.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/os.h"
|
||||
#include "engine/profiler.h"
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
namespace Lumix
|
||||
|
@ -10,50 +12,102 @@ namespace Lumix
|
|||
namespace MT
|
||||
{
|
||||
|
||||
|
||||
void sleep(u32 milliseconds)
|
||||
struct TaskImpl
|
||||
{
|
||||
if (milliseconds) usleep(useconds_t(milliseconds * 1000));
|
||||
IAllocator& allocator;
|
||||
bool force_exit;
|
||||
bool exited;
|
||||
bool is_running;
|
||||
pthread_t handle;
|
||||
const char* thread_name;
|
||||
Task* owner;
|
||||
ConditionVariable cv;
|
||||
};
|
||||
|
||||
static void* threadFunction(void* ptr)
|
||||
{
|
||||
struct TaskImpl* impl = reinterpret_cast<TaskImpl*>(ptr);
|
||||
pthread_setname_np(OS::getCurrentThreadID(), impl->thread_name);
|
||||
Profiler::setThreadName(impl->thread_name);
|
||||
u32 ret = 0xffffFFFF;
|
||||
if (!impl->force_exit) ret = impl->owner->task();
|
||||
impl->exited = true;
|
||||
impl->is_running = false;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void yield()
|
||||
Task::Task(IAllocator& allocator)
|
||||
{
|
||||
pthread_yield();
|
||||
auto impl = LUMIX_NEW(allocator, TaskImpl) {allocator};
|
||||
|
||||
impl->is_running = false;
|
||||
impl->force_exit = false;
|
||||
impl->exited = false;
|
||||
impl->thread_name = "";
|
||||
impl->owner = this;
|
||||
|
||||
m_implementation = impl;
|
||||
}
|
||||
|
||||
|
||||
u32 getCPUsCount()
|
||||
Task::~Task()
|
||||
{
|
||||
return sysconf(_SC_NPROCESSORS_ONLN);
|
||||
LUMIX_DELETE(m_implementation->allocator, m_implementation);
|
||||
}
|
||||
|
||||
ThreadID getCurrentThreadID()
|
||||
{
|
||||
return pthread_self();
|
||||
void Task::sleep(CriticalSection& cs) {
|
||||
ASSERT(pthread_self() == m_implementation->handle);
|
||||
m_implementation->cv.sleep(cs);
|
||||
}
|
||||
|
||||
u64 getThreadAffinityMask()
|
||||
{
|
||||
cpu_set_t cpu_set;
|
||||
int r = pthread_getaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
|
||||
ASSERT(r == 0);
|
||||
if(CPU_COUNT(&cpu_set) == 0) return 0;
|
||||
void Task::wakeup() { m_implementation->cv.wakeup(); }
|
||||
|
||||
int affinity = 0;
|
||||
for(u64 i = 0; i < sizeof(u64) * 8; ++i)
|
||||
bool Task::create(const char* name, bool is_extended)
|
||||
{
|
||||
pthread_attr_t attr;
|
||||
int res = pthread_attr_init(&attr);
|
||||
ASSERT(res == 0);
|
||||
if (res != 0) return false;
|
||||
res = pthread_create(&m_implementation->handle, &attr, threadFunction, m_implementation);
|
||||
ASSERT(res == 0);
|
||||
if (res != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Task::destroy()
|
||||
{
|
||||
return pthread_join(m_implementation->handle, nullptr) == 0;
|
||||
}
|
||||
|
||||
void Task::setAffinityMask(u64 affinity_mask)
|
||||
{
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
if (CPU_ISSET(i, &cpu_set)) affinity = affinity | (1 << i);
|
||||
if (affinity_mask & ((u64)1 << i))
|
||||
{
|
||||
CPU_SET(i, &set);
|
||||
}
|
||||
}
|
||||
return affinity;
|
||||
pthread_setaffinity_np(m_implementation->handle, sizeof(set), &set);
|
||||
}
|
||||
|
||||
|
||||
void setThreadName(ThreadID thread_id, const char* thread_name)
|
||||
bool Task::isRunning() const
|
||||
{
|
||||
pthread_setname_np(thread_id, thread_name);
|
||||
return m_implementation->is_running;
|
||||
}
|
||||
|
||||
bool Task::isFinished() const
|
||||
{
|
||||
return m_implementation->exited;
|
||||
}
|
||||
|
||||
IAllocator& Task::getAllocator()
|
||||
{
|
||||
return m_implementation->allocator;
|
||||
}
|
||||
|
||||
} // namespace MT
|
||||
} // namespace Lumix
|
||||
|
||||
} //! namespace MT
|
||||
} //! namespace Lumix
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace MT
|
|||
|
||||
class alignas(8) LUMIX_ENGINE_API CriticalSection
|
||||
{
|
||||
friend class ConditionVariable;
|
||||
public:
|
||||
CriticalSection();
|
||||
~CriticalSection();
|
||||
|
@ -55,31 +56,25 @@ public:
|
|||
void signal();
|
||||
|
||||
void wait();
|
||||
bool poll();
|
||||
|
||||
private:
|
||||
SemaphoreHandle m_id;
|
||||
};
|
||||
|
||||
|
||||
class LUMIX_ENGINE_API Event
|
||||
class ConditionVariable
|
||||
{
|
||||
public:
|
||||
explicit Event();
|
||||
~Event();
|
||||
|
||||
void reset();
|
||||
|
||||
void trigger();
|
||||
|
||||
void wait();
|
||||
void waitTimeout(u32 timeout_ms);
|
||||
bool poll();
|
||||
|
||||
static void waitMultiple(Event& event0, Event& event1, u32 timeout_ms);
|
||||
|
||||
ConditionVariable();
|
||||
~ConditionVariable();
|
||||
void sleep(CriticalSection& cs);
|
||||
void wakeup();
|
||||
private:
|
||||
EventHandle m_id;
|
||||
#ifdef _WIN32
|
||||
u8 data[64];
|
||||
#else
|
||||
pthread_cond_t cv;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ struct IAllocator;
|
|||
namespace MT
|
||||
{
|
||||
|
||||
class CriticalSection;
|
||||
|
||||
class LUMIX_ENGINE_API Task
|
||||
{
|
||||
|
@ -25,6 +26,10 @@ public:
|
|||
|
||||
void setAffinityMask(u64 affinity_mask);
|
||||
|
||||
// call only from task's thread
|
||||
void sleep(CriticalSection& cs);
|
||||
void wakeup();
|
||||
|
||||
bool isRunning() const;
|
||||
bool isFinished() const;
|
||||
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "engine/lumix.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace Lumix { namespace MT {
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef u32 ThreadID;
|
||||
#else
|
||||
typedef pthread_t ThreadID;
|
||||
#endif
|
||||
|
||||
|
||||
LUMIX_ENGINE_API void setThreadName(ThreadID thread_id, const char* thread_name);
|
||||
LUMIX_ENGINE_API void sleep(u32 milliseconds);
|
||||
LUMIX_ENGINE_API void yield();
|
||||
LUMIX_ENGINE_API u32 getCPUsCount();
|
||||
LUMIX_ENGINE_API ThreadID getCurrentThreadID();
|
||||
LUMIX_ENGINE_API u64 getThreadAffinityMask();
|
||||
|
||||
|
||||
} // namespace MT
|
||||
} // namespace Lumix
|
|
@ -2,7 +2,6 @@
|
|||
#include "engine/crt.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/mt/atomic.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/profiler.h"
|
||||
#include "engine/string.h"
|
||||
#include "engine/win/simple_win.h"
|
||||
|
@ -35,53 +34,20 @@ void Semaphore::wait()
|
|||
::WaitForSingleObject(m_id, INFINITE);
|
||||
}
|
||||
|
||||
bool Semaphore::poll()
|
||||
{
|
||||
return WAIT_OBJECT_0 == ::WaitForSingleObject(m_id, 0);
|
||||
ConditionVariable::~ConditionVariable() {
|
||||
((CONDITION_VARIABLE*)data)->~CONDITION_VARIABLE();
|
||||
}
|
||||
|
||||
|
||||
Event::Event()
|
||||
{
|
||||
m_id = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
Event::~Event()
|
||||
{
|
||||
::CloseHandle(m_id);
|
||||
}
|
||||
|
||||
void Event::reset()
|
||||
{
|
||||
::ResetEvent(m_id);
|
||||
}
|
||||
|
||||
void Event::trigger()
|
||||
{
|
||||
::SetEvent(m_id);
|
||||
}
|
||||
|
||||
void Event::waitMultiple(Event& event0, Event& event1, u32 timeout_ms)
|
||||
{
|
||||
const HANDLE handles[2] = { event0.m_id, event1.m_id };
|
||||
::WaitForMultipleObjects(2, handles, false, timeout_ms);
|
||||
}
|
||||
|
||||
void Event::waitTimeout(u32 timeout_ms)
|
||||
{
|
||||
::WaitForSingleObject(m_id, timeout_ms);
|
||||
}
|
||||
|
||||
void Event::wait()
|
||||
{
|
||||
::WaitForSingleObject(m_id, INFINITE);
|
||||
}
|
||||
|
||||
bool Event::poll()
|
||||
{
|
||||
return WAIT_OBJECT_0 == ::WaitForSingleObject(m_id, 0);
|
||||
ConditionVariable::ConditionVariable() {
|
||||
static_assert(sizeof(data) >= sizeof(CONDITION_VARIABLE), "Size is not enough");
|
||||
static_assert(alignof(CONDITION_VARIABLE) == alignof(CONDITION_VARIABLE), "Alignment does not match");
|
||||
memset(data, 0, sizeof(data));
|
||||
CONDITION_VARIABLE* cv = new (NewPlaceholder(), data) CONDITION_VARIABLE;
|
||||
InitializeConditionVariable(cv);
|
||||
}
|
||||
|
||||
void ConditionVariable::sleep(CriticalSection& cs) { SleepConditionVariableCS((CONDITION_VARIABLE*)data, (CRITICAL_SECTION*)cs.data, INFINITE); }
|
||||
void ConditionVariable::wakeup() { WakeConditionVariable((CONDITION_VARIABLE*)data); }
|
||||
|
||||
CriticalSection::CriticalSection()
|
||||
{
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#include "engine/lumix.h"
|
||||
#include "engine/allocator.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/mt/task.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/os.h"
|
||||
#include "engine/win/simple_win.h"
|
||||
#include "engine/profiler.h"
|
||||
|
||||
|
@ -29,9 +30,39 @@ struct TaskImpl
|
|||
volatile bool m_is_running;
|
||||
volatile bool m_exited;
|
||||
const char* m_thread_name;
|
||||
MT::ConditionVariable m_cv;
|
||||
Task* m_owner;
|
||||
};
|
||||
|
||||
static const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
#pragma pack(push,8)
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD type;
|
||||
LPCSTR name;
|
||||
DWORD thread_id;
|
||||
DWORD flags;
|
||||
} THREADNAME_INFO;
|
||||
#pragma pack(pop)
|
||||
|
||||
static void setThreadName(OS::ThreadID thread_id, const char* thread_name)
|
||||
{
|
||||
THREADNAME_INFO info;
|
||||
info.type = 0x1000;
|
||||
info.name = thread_name;
|
||||
info.thread_id = thread_id;
|
||||
info.flags = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD WINAPI threadFunction(LPVOID ptr)
|
||||
{
|
||||
struct TaskImpl* impl = reinterpret_cast<TaskImpl*>(ptr);
|
||||
|
@ -47,7 +78,6 @@ Task::Task(IAllocator& allocator)
|
|||
{
|
||||
TaskImpl* impl = LUMIX_NEW(allocator, TaskImpl)(allocator);
|
||||
impl->m_handle = nullptr;
|
||||
impl->m_affinity_mask = getThreadAffinityMask();
|
||||
impl->m_priority = ::GetThreadPriority(GetCurrentThread());
|
||||
impl->m_is_running = false;
|
||||
impl->m_exited = false;
|
||||
|
@ -73,7 +103,6 @@ bool Task::create(const char* name, bool is_extended)
|
|||
m_implementation->m_thread_name = name;
|
||||
m_implementation->m_handle = handle;
|
||||
m_implementation->m_is_running = true;
|
||||
SetThreadAffinityMask(handle, m_implementation->m_affinity_mask);
|
||||
|
||||
bool success = ::ResumeThread(m_implementation->m_handle) != -1;
|
||||
if (success)
|
||||
|
@ -89,10 +118,7 @@ bool Task::create(const char* name, bool is_extended)
|
|||
|
||||
bool Task::destroy()
|
||||
{
|
||||
while (m_implementation->m_is_running)
|
||||
{
|
||||
yield();
|
||||
}
|
||||
while (m_implementation->m_is_running) OS::sleep(1);
|
||||
|
||||
::CloseHandle(m_implementation->m_handle);
|
||||
m_implementation->m_handle = nullptr;
|
||||
|
@ -108,6 +134,14 @@ void Task::setAffinityMask(u64 affinity_mask)
|
|||
}
|
||||
}
|
||||
|
||||
void Task::sleep(CriticalSection& cs) {
|
||||
m_implementation->m_cv.sleep(cs);
|
||||
}
|
||||
|
||||
void Task::wakeup() {
|
||||
m_implementation->m_cv.wakeup();
|
||||
}
|
||||
|
||||
bool Task::isRunning() const
|
||||
{
|
||||
return m_implementation->m_is_running;
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
#include "engine/lumix.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/win/simple_win.h"
|
||||
|
||||
|
||||
namespace Lumix
|
||||
{
|
||||
namespace MT
|
||||
{
|
||||
static_assert(sizeof(ThreadID) == sizeof(::GetCurrentThreadId()), "Not matching");
|
||||
|
||||
void sleep(u32 milliseconds) { ::Sleep(milliseconds); }
|
||||
void yield() { sleep(0); }
|
||||
|
||||
u32 getCPUsCount()
|
||||
{
|
||||
SYSTEM_INFO sys_info;
|
||||
GetSystemInfo(&sys_info);
|
||||
|
||||
u32 num = sys_info.dwNumberOfProcessors;
|
||||
num = num > 0 ? num : 1;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
ThreadID getCurrentThreadID() { return ::GetCurrentThreadId(); }
|
||||
|
||||
u64 getThreadAffinityMask()
|
||||
{
|
||||
DWORD_PTR affinity_mask = ::SetThreadAffinityMask(::GetCurrentThread(), ~(DWORD_PTR)0);
|
||||
::SetThreadAffinityMask(::GetCurrentThread(), affinity_mask);
|
||||
return affinity_mask;
|
||||
}
|
||||
|
||||
static const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
#pragma pack(push,8)
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD type;
|
||||
LPCSTR name;
|
||||
DWORD thread_id;
|
||||
DWORD flags;
|
||||
} THREADNAME_INFO;
|
||||
#pragma pack(pop)
|
||||
|
||||
void setThreadName(ThreadID thread_id, const char* thread_name)
|
||||
{
|
||||
THREADNAME_INFO info;
|
||||
info.type = 0x1000;
|
||||
info.name = thread_name;
|
||||
info.thread_id = thread_id;
|
||||
info.flags = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
}
|
||||
} //!namespace MT
|
||||
} //!namespace Lumix
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include "lumix.h"
|
||||
#include "stream.h"
|
||||
#ifdef __linux__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace Lumix
|
||||
|
@ -13,6 +16,12 @@ struct IAllocator;
|
|||
namespace OS
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef u32 ThreadID;
|
||||
#else
|
||||
typedef pthread_t ThreadID;
|
||||
#endif
|
||||
|
||||
enum class Keycode : u8;
|
||||
|
||||
enum class ExecuteOpenResult : int
|
||||
|
@ -158,6 +167,9 @@ struct WindowState {
|
|||
};
|
||||
|
||||
LUMIX_ENGINE_API void logVersion();
|
||||
LUMIX_ENGINE_API u32 getCPUsCount();
|
||||
LUMIX_ENGINE_API void sleep(u32 milliseconds);
|
||||
LUMIX_ENGINE_API ThreadID getCurrentThreadID();
|
||||
|
||||
LUMIX_ENGINE_API void* memReserve(size_t size);
|
||||
LUMIX_ENGINE_API void memCommit(void* ptr, size_t size);
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "engine/mt/atomic.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/mt/task.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/os.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
@ -155,7 +154,7 @@ static struct Instance
|
|||
{
|
||||
thread_local ThreadContext* ctx = [&](){
|
||||
ThreadContext* new_ctx = LUMIX_NEW(allocator, ThreadContext)(allocator);
|
||||
new_ctx->thread_id = MT::getCurrentThreadID();
|
||||
new_ctx->thread_id = OS::getCurrentThreadID();
|
||||
MT::CriticalSectionLock lock(mutex);
|
||||
contexts.push(new_ctx);
|
||||
return new_ctx;
|
||||
|
|
|
@ -229,6 +229,20 @@ struct WCharStr
|
|||
WCHAR data[N];
|
||||
};
|
||||
|
||||
void sleep(u32 milliseconds) { ::Sleep(milliseconds); }
|
||||
|
||||
static_assert(sizeof(ThreadID) == sizeof(::GetCurrentThreadId()), "Not matching");
|
||||
ThreadID getCurrentThreadID() { return ::GetCurrentThreadId(); }
|
||||
|
||||
u32 getCPUsCount() {
|
||||
SYSTEM_INFO sys_info;
|
||||
GetSystemInfo(&sys_info);
|
||||
|
||||
u32 num = sys_info.dwNumberOfProcessors;
|
||||
num = num > 0 ? num : 1;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
void logVersion() {
|
||||
DWORD dwVersion = 0;
|
||||
|
|
|
@ -284,6 +284,13 @@ typedef struct _LIST_ENTRY {
|
|||
struct _LIST_ENTRY *Blink;
|
||||
} LIST_ENTRY, *PLIST_ENTRY;
|
||||
|
||||
|
||||
typedef struct _RTL_CONDITION_VARIABLE {
|
||||
PVOID Ptr;
|
||||
} RTL_CONDITION_VARIABLE, *PRTL_CONDITION_VARIABLE;
|
||||
|
||||
typedef RTL_CONDITION_VARIABLE CONDITION_VARIABLE, *PCONDITION_VARIABLE;
|
||||
|
||||
typedef struct _RTL_CRITICAL_SECTION_DEBUG {
|
||||
WORD Type;
|
||||
WORD CreatorBackTraceIndex;
|
||||
|
@ -311,6 +318,7 @@ typedef struct _RTL_CRITICAL_SECTION {
|
|||
ULONG_PTR SpinCount; // force size on 64-bit systems when packed
|
||||
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
|
||||
typedef PRTL_CRITICAL_SECTION LPCRITICAL_SECTION;
|
||||
typedef PRTL_CRITICAL_SECTION PCRITICAL_SECTION;
|
||||
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
|
||||
|
||||
|
||||
|
@ -550,6 +558,9 @@ WINBASEAPI VOID WINAPI DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSectio
|
|||
WINBASEAPI VOID WINAPI EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
|
||||
WINBASEAPI VOID WINAPI LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
|
||||
|
||||
WINBASEAPI BOOL WINAPI SleepConditionVariableCS(PCONDITION_VARIABLE ConditionVariable, PCRITICAL_SECTION CriticalSection, DWORD dwMilliseconds);
|
||||
WINBASEAPI VOID WINAPI InitializeConditionVariable(PCONDITION_VARIABLE ConditionVariable);
|
||||
WINBASEAPI VOID WINAPI WakeConditionVariable(PCONDITION_VARIABLE ConditionVariable);
|
||||
|
||||
WINBASEAPI HANDLE WINAPI CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
|
||||
LONG lInitialCount,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "engine/log.h"
|
||||
#include "engine/lua_wrapper.h"
|
||||
#include "engine/math.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/os.h"
|
||||
#include "engine/path.h"
|
||||
#include "engine/profiler.h"
|
||||
#include "engine/reflection.h"
|
||||
|
@ -244,7 +244,7 @@ struct PhysicsSceneImpl final : public PhysicsScene
|
|||
},
|
||||
nullptr);
|
||||
}
|
||||
PxU32 getWorkerCount() const override { return MT::getCPUsCount(); }
|
||||
PxU32 getWorkerCount() const override { return OS::getCPUsCount(); }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "engine/job_system.h"
|
||||
#include "engine/lumix.h"
|
||||
#include "engine/math.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/page_allocator.h"
|
||||
#include "engine/profiler.h"
|
||||
#include "engine/simd.h"
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "engine/log.h"
|
||||
#include "engine/lua_wrapper.h"
|
||||
#include "engine/lumix.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/os.h"
|
||||
#include "engine/path_utils.h"
|
||||
#include "engine/plugin_manager.h"
|
||||
|
@ -2942,7 +2941,7 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
, 10
|
||||
, 1
|
||||
, cmft::EdgeFixup::None
|
||||
, MT::getCPUsCount()
|
||||
, OS::getCPUsCount()
|
||||
);
|
||||
}
|
||||
cmft::imageFromRgba32f(image, cmft::TextureFormat::RGBA8);
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "engine/log.h"
|
||||
#include "engine/math.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/os.h"
|
||||
#include "engine/stream.h"
|
||||
#ifdef _WIN32
|
||||
|
@ -116,7 +115,7 @@ static struct {
|
|||
Pool<Texture, Texture::MAX_COUNT> textures;
|
||||
Pool<Program, Program::MAX_COUNT> programs;
|
||||
MT::CriticalSection handle_mutex;
|
||||
Lumix::MT::ThreadID thread;
|
||||
Lumix::OS::ThreadID thread;
|
||||
int instance_attributes = 0;
|
||||
int max_vertex_attributes = 16;
|
||||
ProgramHandle last_program = INVALID_PROGRAM;
|
||||
|
@ -713,7 +712,7 @@ static void flipCompressedTexture(int w, int h, int format, void* surface)
|
|||
|
||||
void checkThread()
|
||||
{
|
||||
ASSERT(g_gpu.thread == Lumix::MT::getCurrentThreadID());
|
||||
ASSERT(g_gpu.thread == OS::getCurrentThreadID());
|
||||
}
|
||||
|
||||
static void try_load_renderdoc()
|
||||
|
@ -2125,7 +2124,7 @@ bool init(void* window_handle, u32 init_flags)
|
|||
const bool debug = init_flags & (u32)InitFlags::DEBUG_OUTPUT;
|
||||
#endif
|
||||
|
||||
g_gpu.thread = MT::getCurrentThreadID();
|
||||
g_gpu.thread = OS::getCurrentThreadID();
|
||||
g_gpu.contexts[0].window_handle = window_handle;
|
||||
#ifdef _WIN32
|
||||
g_gpu.contexts[0].device_context = GetDC((HWND)window_handle);
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "engine/job_system.h"
|
||||
#include "engine/mt/sync.h"
|
||||
#include "engine/mt/task.h"
|
||||
#include "engine/mt/thread.h"
|
||||
#include "engine/os.h"
|
||||
#include "engine/profiler.h"
|
||||
#include "engine/reflection.h"
|
||||
|
@ -209,7 +208,7 @@ struct GPUProfiler
|
|||
const u64 cpu_timestamp = OS::Timer::getRawTimestamp();
|
||||
u32 try_num = 0;
|
||||
while (!gpu::isQueryReady(q) && try_num < 1000) {
|
||||
MT::sleep(1);
|
||||
OS::sleep(1);
|
||||
++try_num;
|
||||
}
|
||||
if (try_num == 1000) {
|
||||
|
|
Loading…
Reference in a new issue