Merge pull request #1266 from alliedmodders/threads-3
Pare down ThreadSupport and remove ancient thread code.
This commit is contained in:
commit
15023777f4
128
.travis.yml
128
.travis.yml
@ -3,110 +3,80 @@ git:
|
||||
|
||||
sudo: false
|
||||
language: cpp
|
||||
os: linux
|
||||
dist: trusty
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty-3.9
|
||||
- llvm-toolchain-trusty-4.0
|
||||
- llvm-toolchain-trusty-5.0
|
||||
packages:
|
||||
- lib32stdc++6
|
||||
- lib32z1-dev
|
||||
- libc6-dev-i386
|
||||
- linux-libc-dev
|
||||
- g++-multilib
|
||||
# - clang-3.6
|
||||
# - clang-3.8
|
||||
# - clang-4.0
|
||||
# - clang-5.0
|
||||
# - g++-6
|
||||
# - g++-6-multilib
|
||||
- clang-3.9
|
||||
- g++-4.8-multilib
|
||||
- g++-4.8
|
||||
- g++-4.9-multilib
|
||||
- g++-4.9
|
||||
- g++-5-multilib
|
||||
- g++-5
|
||||
- g++-7-multilib
|
||||
- g++-7
|
||||
cache:
|
||||
directories:
|
||||
- ../mysql-5.0
|
||||
env:
|
||||
- MATRIX_EVAL="CC=clang-3.9 && CXX=clang++-3.9"
|
||||
- MATRIX_EVAL="CC=gcc-4.8 && CXX=g++-4.8"
|
||||
- MATRIX_EVAL="CC=gcc-4.9 && CXX=g++-4.9"
|
||||
- MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
|
||||
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: cpp
|
||||
addons:
|
||||
apt:
|
||||
packages: ['clang-3.6', 'lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev', 'g++-multilib']
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages: ['clang-3.8', 'lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev:i386', 'g++-4.9-multilib']
|
||||
cache:
|
||||
directories: ['../mysql-5.0']
|
||||
env: ['MATRIX_EVAL="CC=clang-3.6 && CXX=clang++-3.6"']
|
||||
env:
|
||||
- MATRIX_EVAL="CC=clang-3.8 && CXX=clang++-3.8"
|
||||
- SDKS=episode1,css,tf2,l4d2,csgo,dota
|
||||
- MODE=optimize
|
||||
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: cpp
|
||||
addons:
|
||||
apt:
|
||||
packages: ['clang-3.8', 'lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev', 'g++-multilib']
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages: ['clang-3.4', 'lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev:i386', 'g++-4.9-multilib']
|
||||
cache:
|
||||
directories: ['../mysql-5.0']
|
||||
env: ['MATRIX_EVAL="CC=clang-3.8 && CXX=clang++-3.8"']
|
||||
env:
|
||||
- MATRIX_EVAL="CC=clang && CXX=clang++"
|
||||
- SDKS=episode1,css,tf2,l4d2,csgo,dota
|
||||
- MODE=optimize
|
||||
|
||||
- os: osx
|
||||
osx_image: xcode7.2
|
||||
language: cpp
|
||||
env:
|
||||
- MATRIX_EVAL="CC=clang && CXX=clang++"
|
||||
- SDKS=episode1,css,tf2,l4d2,csgo,dota
|
||||
- MODE=optimize
|
||||
|
||||
# # This is a faster test for the latest g++.
|
||||
# - os: linux
|
||||
# dist: bionic
|
||||
# sudo: false
|
||||
# language: cpp
|
||||
# addons:
|
||||
# apt:
|
||||
# packages: ['lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev:i386', 'g++-multilib', 'g++']
|
||||
# cache:
|
||||
# directories: ['../mysql-5.0']
|
||||
# env:
|
||||
# - MATRIX_EVAL="CC=gcc && CXX=g++"
|
||||
# - SDKS=csgo
|
||||
# # GCC currently fails in opt builds trying to inline stuff in sqlite3.c.
|
||||
# - MODE=debug
|
||||
|
||||
# This is a faster test for the latest clang.
|
||||
- os: linux
|
||||
dist: bionic
|
||||
sudo: false
|
||||
language: cpp
|
||||
addons:
|
||||
apt:
|
||||
sources: ['llvm-toolchain-trusty-4.0']
|
||||
packages: ['clang-4.0', 'lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev', 'g++-multilib']
|
||||
packages: ['lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev:i386', 'g++-multilib', 'clang']
|
||||
cache:
|
||||
directories: ['../mysql-5.0']
|
||||
env: ['MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0"']
|
||||
|
||||
- os: linux
|
||||
sudo: false
|
||||
language: cpp
|
||||
addons:
|
||||
apt:
|
||||
sources: ['llvm-toolchain-trusty-5.0']
|
||||
packages: ['clang-5.0', 'lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev', 'g++-multilib']
|
||||
cache:
|
||||
directories: ['../mysql-5.0']
|
||||
env: ['MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0"']
|
||||
|
||||
- os: linux
|
||||
sudo: false
|
||||
language: cpp
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-6', 'g++-6-multilib', 'lib32stdc++6', 'lib32z1-dev', 'libc6-dev-i386', 'linux-libc-dev', 'g++-multilib']
|
||||
cache:
|
||||
directories: ['../mysql-5.0']
|
||||
env: ['MATRIX_EVAL="CC=gcc-6 && CXX=g++-6"']
|
||||
|
||||
allow_failures:
|
||||
- env: MATRIX_EVAL="CC=clang-3.7 && CXX=clang++-3.7"
|
||||
- env: MATRIX_EVAL="CC=clang-3.9 && CXX=clang++-3.9"
|
||||
- env: MATRIX_EVAL="CC=gcc-4.8 && CXX=g++-4.8"
|
||||
- env: MATRIX_EVAL="CC=gcc-4.9 && CXX=g++-4.9"
|
||||
- env: MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
|
||||
- env: MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
|
||||
|
||||
env:
|
||||
- MATRIX_EVAL="CC=clang && CXX=clang++"
|
||||
- SDKS=csgo
|
||||
- MODE=optimize
|
||||
|
||||
before_script:
|
||||
- CHECKOUT_DIR=$PWD && cd .. && $CHECKOUT_DIR/tools/checkout-deps.sh && cd $CHECKOUT_DIR
|
||||
@ -114,5 +84,7 @@ script:
|
||||
- mkdir build && cd build
|
||||
- PATH="~/.local/bin:$PATH"
|
||||
- eval "${MATRIX_EVAL}"
|
||||
- python ../configure.py --enable-optimize --sdks=episode1,css,tf2,l4d2,csgo,dota
|
||||
- eval "${CC} --version"
|
||||
- eval "${CXX} --version"
|
||||
- python ../configure.py --enable-${MODE} --sdks=${SDKS}
|
||||
- ambuild
|
||||
|
@ -281,8 +281,13 @@ class SMConfig(object):
|
||||
'-msse',
|
||||
'-fvisibility=hidden',
|
||||
]
|
||||
|
||||
if cxx.version == 'apple-clang-6.0' or cxx.version == 'clang-3.4':
|
||||
cxx.cxxflags += ['-std=c++1y']
|
||||
else:
|
||||
cxx.cxxflags += ['-std=c++14']
|
||||
|
||||
cxx.cxxflags += [
|
||||
'-std=c++11',
|
||||
'-fno-exceptions',
|
||||
'-fno-threadsafe-statics',
|
||||
'-Wno-non-virtual-dtor',
|
||||
@ -292,11 +297,11 @@ class SMConfig(object):
|
||||
|
||||
have_gcc = cxx.family == 'gcc'
|
||||
have_clang = cxx.family == 'clang'
|
||||
if cxx.version >= 'clang-3.9':
|
||||
if cxx.version >= 'clang-3.9' or cxx.version == 'clang-3.4' or cxx.version > 'apple-clang-6.0':
|
||||
cxx.cxxflags += ['-Wno-expansion-to-defined']
|
||||
if cxx.version == 'clang-3.9' or cxx.version == 'apple-clang-8.0':
|
||||
cxx.cflags += ['-Wno-varargs']
|
||||
if cxx.version >= 'clang-3.6' or cxx.version >= 'apple-clang-7.0':
|
||||
if cxx.version >= 'clang-3.4' or cxx.version >= 'apple-clang-7.0':
|
||||
cxx.cxxflags += ['-Wno-inconsistent-missing-override']
|
||||
if cxx.version >= 'clang-2.9' or cxx.version >= 'apple-clang-3.0':
|
||||
cxx.cxxflags += ['-Wno-null-dereference']
|
||||
@ -316,6 +321,13 @@ class SMConfig(object):
|
||||
cxx.cxxflags += ['-Wno-deprecated']
|
||||
cxx.cflags += ['-Wno-sometimes-uninitialized']
|
||||
|
||||
# Work around SDK warnings.
|
||||
if cxx.version >= 'clang-10.0':
|
||||
cxx.cflags += [
|
||||
'-Wno-implicit-int-float-conversion',
|
||||
'-Wno-tautological-overlap-compare',
|
||||
]
|
||||
|
||||
if have_gcc:
|
||||
cxx.cflags += ['-mfpmath=sse']
|
||||
cxx.cflags += ['-Wno-maybe-uninitialized']
|
||||
@ -379,16 +391,16 @@ class SMConfig(object):
|
||||
cxx.linkflags += ['-static-libgcc']
|
||||
elif cxx.family == 'clang':
|
||||
cxx.linkflags += ['-lgcc_eh']
|
||||
cxx.linkflags += ['-static-libstdc++']
|
||||
|
||||
def configure_mac(self, cxx):
|
||||
cxx.defines += ['OSX', '_OSX', 'POSIX', 'KE_ABSOLUTELY_NO_STL']
|
||||
cxx.cflags += ['-mmacosx-version-min=10.5']
|
||||
cxx.cflags += ['-mmacosx-version-min=10.7']
|
||||
cxx.linkflags += [
|
||||
'-mmacosx-version-min=10.5',
|
||||
'-lstdc++',
|
||||
'-stdlib=libstdc++',
|
||||
'-mmacosx-version-min=10.7',
|
||||
'-stdlib=libc++',
|
||||
]
|
||||
cxx.cxxflags += ['-stdlib=libstdc++']
|
||||
cxx.cxxflags += ['-stdlib=libc++']
|
||||
|
||||
def configure_windows(self, cxx):
|
||||
cxx.defines += ['WIN32', '_WINDOWS']
|
||||
@ -538,9 +550,11 @@ class SMConfig(object):
|
||||
if builder.target.platform in ['linux', 'mac']:
|
||||
compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE']
|
||||
|
||||
if sdk.name in ['csgo', 'blade'] and builder.target.platform == 'linux':
|
||||
compiler.linkflags += ['-lstdc++']
|
||||
compiler.defines += ['_GLIBCXX_USE_CXX11_ABI=0']
|
||||
if builder.target.platform == 'linux':
|
||||
if sdk.name in ['csgo', 'blade']:
|
||||
compiler.linkflags.remove('-static-libstdc++')
|
||||
compiler.linkflags += ['-lstdc++']
|
||||
compiler.defines += ['_GLIBCXX_USE_CXX11_ABI=0']
|
||||
|
||||
for path in paths:
|
||||
compiler.cxxincludes += [os.path.join(sdk.path, *path)]
|
||||
|
@ -35,8 +35,7 @@ for arch in SM.archs:
|
||||
'smn_maplists.cpp',
|
||||
'ADTFactory.cpp',
|
||||
'smn_adt_stack.cpp',
|
||||
'thread/ThreadWorker.cpp',
|
||||
'thread/BaseWorker.cpp',
|
||||
'BaseWorker.cpp',
|
||||
'ThreadSupport.cpp',
|
||||
'smn_float.cpp',
|
||||
'TextParsers.cpp',
|
||||
@ -90,9 +89,4 @@ for arch in SM.archs:
|
||||
if arch == 'x64':
|
||||
binary.sources += ['PseudoAddrManager.cpp']
|
||||
|
||||
if builder.target.platform == 'windows':
|
||||
binary.sources += ['thread/WinThreads.cpp']
|
||||
else:
|
||||
binary.sources += ['thread/PosixThreads.cpp']
|
||||
|
||||
SM.binaries += [builder.Add(binary)]
|
||||
|
@ -43,6 +43,7 @@ class BaseWorker;
|
||||
class SWThreadHandle : public IThreadHandle
|
||||
{
|
||||
friend class BaseWorker;
|
||||
friend class CompatWorker;
|
||||
public:
|
||||
SWThreadHandle(IThreadCreator *parent, const ThreadParams *p, IThread *thread);
|
||||
IThread *GetThread();
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* vim: set ts=4 sw=4 :
|
||||
* vim: set ts=4 sw=4 tw=99 noet :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
|
||||
@ -29,17 +29,515 @@
|
||||
* Version: $Id$
|
||||
*/
|
||||
#include <sm_platform.h>
|
||||
#include <amtl/am-deque.h>
|
||||
#include <amtl/am-maybe.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "BaseWorker.h"
|
||||
#include "ThreadSupport.h"
|
||||
#include "common_logic.h"
|
||||
|
||||
#if defined PLATFORM_POSIX
|
||||
#include "thread/PosixThreads.h"
|
||||
#elif defined PLATFORM_WINDOWS
|
||||
#include "thread/WinThreads.h"
|
||||
#endif
|
||||
static constexpr unsigned int DEFAULT_THINK_TIME_MS = 20;
|
||||
|
||||
MainThreader g_MainThreader;
|
||||
IThreader *g_pThreader = &g_MainThreader;
|
||||
class CompatWorker final : public IThreadWorker
|
||||
{
|
||||
public:
|
||||
explicit CompatWorker(IThreadWorkerCallbacks* callbacks);
|
||||
~CompatWorker();
|
||||
|
||||
void MakeThread(IThread *pThread) override;
|
||||
IThreadHandle *MakeThread(IThread *pThread, ThreadFlags flags) override;
|
||||
IThreadHandle *MakeThread(IThread *pThread, const ThreadParams *params) override;
|
||||
void GetPriorityBounds(ThreadPriority &max, ThreadPriority &min) override;
|
||||
unsigned int RunFrame() override;
|
||||
bool Pause() override;
|
||||
bool Unpause() override;
|
||||
bool Start() override;
|
||||
bool Stop(bool flush) override;
|
||||
WorkerState GetStatus(unsigned int *numThreads) override;
|
||||
void SetMaxThreadsPerFrame(unsigned int threads) override;
|
||||
void SetThinkTimePerFrame(unsigned int thinktime) override;
|
||||
|
||||
private:
|
||||
void Flush();
|
||||
void Worker();
|
||||
void RunWork(SWThreadHandle* handle);
|
||||
void RunWorkLocked(std::unique_lock<std::mutex>* lock, SWThreadHandle* handle);
|
||||
|
||||
private:
|
||||
IThreadWorkerCallbacks* callbacks_;
|
||||
WorkerState state_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable work_cv_;
|
||||
ke::Deque<SWThreadHandle*> work_;
|
||||
std::unique_ptr<std::thread> thread_;
|
||||
std::atomic<unsigned int> jobs_per_wakeup_;
|
||||
std::atomic<unsigned int> wait_between_jobs_;
|
||||
};
|
||||
|
||||
CompatWorker::CompatWorker(IThreadWorkerCallbacks* callbacks)
|
||||
: callbacks_(callbacks),
|
||||
state_(Worker_Stopped),
|
||||
jobs_per_wakeup_(SM_DEFAULT_THREADS_PER_FRAME),
|
||||
wait_between_jobs_(DEFAULT_THINK_TIME_MS)
|
||||
{
|
||||
}
|
||||
|
||||
CompatWorker::~CompatWorker()
|
||||
{
|
||||
Stop(false /* ignored */);
|
||||
Flush();
|
||||
}
|
||||
|
||||
bool CompatWorker::Start()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != Worker_Stopped)
|
||||
return false;
|
||||
|
||||
thread_ = std::make_unique<std::thread>([this]() -> void {
|
||||
Worker();
|
||||
});
|
||||
state_ = Worker_Running;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompatWorker::Stop(bool)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (state_ <= Worker_Stopped)
|
||||
return false;
|
||||
|
||||
state_ = Worker_Stopped;
|
||||
work_cv_.notify_all();
|
||||
}
|
||||
|
||||
thread_->join();
|
||||
thread_ = nullptr;
|
||||
|
||||
Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompatWorker::Pause()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != Worker_Running)
|
||||
return false;
|
||||
|
||||
state_ = Worker_Paused;
|
||||
work_cv_.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompatWorker::Unpause()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != Worker_Paused)
|
||||
return false;
|
||||
|
||||
state_ = Worker_Running;
|
||||
work_cv_.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompatWorker::Flush()
|
||||
{
|
||||
while (!work_.empty()) {
|
||||
auto handle = work_.popFrontCopy();
|
||||
handle->GetThread()->OnTerminate(handle, true);
|
||||
if (handle->m_params.flags & Thread_AutoRelease)
|
||||
delete handle;
|
||||
}
|
||||
}
|
||||
|
||||
void CompatWorker::Worker()
|
||||
{
|
||||
// Note: this must be first to ensure an ordering between Worker() and
|
||||
// Start(). It must also be outside of the loop to ensure the lock is
|
||||
// held across wakeup and retesting the predicates.
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
if (callbacks_) {
|
||||
lock.unlock();
|
||||
callbacks_->OnWorkerStart(this);
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
typedef std::chrono::system_clock Clock;
|
||||
typedef std::chrono::time_point<Clock> TimePoint;
|
||||
|
||||
auto can_work = [this]() -> bool {
|
||||
return state_ == Worker_Running && !work_.empty();
|
||||
};
|
||||
|
||||
ke::Maybe<TimePoint> wait;
|
||||
unsigned int work_in_frame = 0;
|
||||
for (;;) {
|
||||
if (state_ == Worker_Stopped)
|
||||
break;
|
||||
|
||||
if (!can_work()) {
|
||||
// Wait for work or a Stop.
|
||||
work_cv_.wait(lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wait.isValid()) {
|
||||
// Wait until the specified time has passed. If we wake up with a
|
||||
// timeout, then the wait has elapsed, so reset the holder.
|
||||
if (work_cv_.wait_until(lock, wait.get()) == std::cv_status::timeout)
|
||||
wait = ke::Nothing();
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(state_ == Worker_Running);
|
||||
assert(!work_.empty());
|
||||
|
||||
SWThreadHandle* handle = work_.popFrontCopy();
|
||||
RunWorkLocked(&lock, handle);
|
||||
work_in_frame++;
|
||||
|
||||
// If we've reached our max jobs per "frame", signal that the next
|
||||
// immediate job must be delayed. We retain the old ThreadWorker
|
||||
// behavior by checking if the queue has more work. Thus, a delay
|
||||
// only occurs if two jobs would be processed in the same wakeup.
|
||||
if (work_in_frame >= jobs_per_wakeup_ && wait_between_jobs_ && can_work())
|
||||
wait = ke::Some(Clock::now() + std::chrono::milliseconds(wait_between_jobs_));
|
||||
}
|
||||
|
||||
assert(lock.owns_lock());
|
||||
|
||||
while (!work_.empty()) {
|
||||
SWThreadHandle* handle = work_.popFrontCopy();
|
||||
RunWorkLocked(&lock, handle);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int CompatWorker::RunFrame()
|
||||
{
|
||||
unsigned int nprocessed = 0;
|
||||
for (unsigned int i = 1; i <= jobs_per_wakeup_; i++) {
|
||||
SWThreadHandle* handle;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (work_.empty())
|
||||
break;
|
||||
handle = work_.popFrontCopy();
|
||||
}
|
||||
|
||||
RunWork(handle);
|
||||
nprocessed++;
|
||||
}
|
||||
return nprocessed;
|
||||
}
|
||||
|
||||
void CompatWorker::RunWorkLocked(std::unique_lock<std::mutex>* lock, SWThreadHandle* handle)
|
||||
{
|
||||
lock->unlock();
|
||||
RunWork(handle);
|
||||
lock->lock();
|
||||
}
|
||||
|
||||
void CompatWorker::RunWork(SWThreadHandle* handle)
|
||||
{
|
||||
bool autorelease = !!(handle->m_params.flags & Thread_AutoRelease);
|
||||
handle->m_state = Thread_Running;
|
||||
handle->GetThread()->RunThread(handle);
|
||||
handle->m_state = Thread_Done;
|
||||
handle->GetThread()->OnTerminate(handle, false);
|
||||
if (autorelease)
|
||||
delete handle;
|
||||
}
|
||||
|
||||
void CompatWorker::MakeThread(IThread *pThread)
|
||||
{
|
||||
ThreadParams params;
|
||||
params.flags = Thread_AutoRelease;
|
||||
MakeThread(pThread, ¶ms);
|
||||
}
|
||||
|
||||
IThreadHandle *CompatWorker::MakeThread(IThread *pThread, ThreadFlags flags)
|
||||
{
|
||||
ThreadParams params;
|
||||
params.flags = flags;
|
||||
return MakeThread(pThread, ¶ms);
|
||||
}
|
||||
|
||||
IThreadHandle *CompatWorker::MakeThread(IThread *pThread, const ThreadParams *params)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
ThreadParams def_params;
|
||||
if (!params)
|
||||
params = &def_params;
|
||||
|
||||
if (state_ <= Worker_Stopped)
|
||||
return nullptr;
|
||||
|
||||
SWThreadHandle* handle = new SWThreadHandle(this, params, pThread);
|
||||
work_.append(handle);
|
||||
work_cv_.notify_one();
|
||||
return handle;
|
||||
}
|
||||
|
||||
void CompatWorker::GetPriorityBounds(ThreadPriority &max, ThreadPriority &min)
|
||||
{
|
||||
min = ThreadPrio_Normal;
|
||||
max = ThreadPrio_Normal;
|
||||
}
|
||||
|
||||
void CompatWorker::SetMaxThreadsPerFrame(unsigned int threads)
|
||||
{
|
||||
jobs_per_wakeup_ = threads;
|
||||
}
|
||||
|
||||
void CompatWorker::SetThinkTimePerFrame(unsigned int thinktime)
|
||||
{
|
||||
wait_between_jobs_ = thinktime;
|
||||
}
|
||||
|
||||
WorkerState CompatWorker::GetStatus(unsigned int *numThreads)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// This number is meaningless and the status is racy.
|
||||
if (numThreads)
|
||||
*numThreads = jobs_per_wakeup_;
|
||||
return state_;
|
||||
}
|
||||
|
||||
class CompatThread final : public IThreadHandle
|
||||
{
|
||||
public:
|
||||
CompatThread(IThread* callbacks, const ThreadParams* params);
|
||||
|
||||
bool WaitForThread() override;
|
||||
void DestroyThis() override;
|
||||
IThreadCreator *Parent() override;
|
||||
void GetParams(ThreadParams *ptparams) override;
|
||||
ThreadPriority GetPriority() override;
|
||||
bool SetPriority(ThreadPriority prio) override;
|
||||
ThreadState GetState() override;
|
||||
bool Unpause() override;
|
||||
|
||||
private:
|
||||
void Run();
|
||||
|
||||
private:
|
||||
IThread* callbacks_;
|
||||
ThreadParams params_;
|
||||
std::unique_ptr<std::thread> thread_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable check_cv_;
|
||||
std::atomic<bool> finished_;
|
||||
};
|
||||
|
||||
CompatThread::CompatThread(IThread* callbacks, const ThreadParams* params)
|
||||
: callbacks_(callbacks),
|
||||
params_(*params)
|
||||
{
|
||||
if (!(params_.flags & Thread_CreateSuspended))
|
||||
Unpause();
|
||||
}
|
||||
|
||||
bool CompatThread::Unpause()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (thread_)
|
||||
return false;
|
||||
|
||||
thread_ = std::make_unique<std::thread>([this]() -> void {
|
||||
Run();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompatThread::Run()
|
||||
{
|
||||
// Create an ordering between when the thread runs and when thread_ is assigned.
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
lock.unlock();
|
||||
callbacks_->RunThread(this);
|
||||
finished_ = true;
|
||||
callbacks_->OnTerminate(this, false);
|
||||
|
||||
if (params_.flags & Thread_AutoRelease) {
|
||||
// There should be no handles outstanding, so it's safe to self-destruct.
|
||||
thread_->detach();
|
||||
delete this;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
callbacks_ = nullptr;
|
||||
check_cv_.notify_all();
|
||||
}
|
||||
|
||||
bool CompatThread::WaitForThread()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
for (;;) {
|
||||
// When done, callbacks are unset. If paused, this will deadlock.
|
||||
if (!callbacks_)
|
||||
break;
|
||||
check_cv_.wait(lock);
|
||||
}
|
||||
|
||||
thread_->join();
|
||||
return true;
|
||||
}
|
||||
|
||||
ThreadState CompatThread::GetState()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (!thread_)
|
||||
return Thread_Paused;
|
||||
return finished_ ? Thread_Done : Thread_Running;
|
||||
}
|
||||
|
||||
void CompatThread::DestroyThis()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
ThreadPriority CompatThread::GetPriority()
|
||||
{
|
||||
return ThreadPrio_Normal;
|
||||
}
|
||||
|
||||
bool CompatThread::SetPriority(ThreadPriority prio)
|
||||
{
|
||||
return prio == ThreadPrio_Normal;
|
||||
}
|
||||
|
||||
IThreadCreator *CompatThread::Parent()
|
||||
{
|
||||
return g_pThreader;
|
||||
}
|
||||
|
||||
void CompatThread::GetParams(ThreadParams *ptparams)
|
||||
{
|
||||
*ptparams = params_;
|
||||
}
|
||||
|
||||
class CompatMutex : public IMutex
|
||||
{
|
||||
public:
|
||||
bool TryLock() {
|
||||
return mutex_.try_lock();
|
||||
}
|
||||
void Lock() {
|
||||
mutex_.lock();
|
||||
}
|
||||
void Unlock() {
|
||||
mutex_.unlock();
|
||||
}
|
||||
void DestroyThis() {
|
||||
delete this;
|
||||
}
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
class CompatThreader final : public IThreader
|
||||
{
|
||||
public:
|
||||
void MakeThread(IThread *pThread) override;
|
||||
IThreadHandle *MakeThread(IThread *pThread, ThreadFlags flags) override;
|
||||
IThreadHandle *MakeThread(IThread *pThread, const ThreadParams *params) override;
|
||||
void GetPriorityBounds(ThreadPriority &max, ThreadPriority &min) override;
|
||||
IMutex *MakeMutex() override;
|
||||
void ThreadSleep(unsigned int ms) override;
|
||||
IEventSignal *MakeEventSignal() override;
|
||||
IThreadWorker *MakeWorker(IThreadWorkerCallbacks *hooks, bool threaded) override;
|
||||
void DestroyWorker(IThreadWorker *pWorker) override;
|
||||
} sCompatThreader;
|
||||
|
||||
void CompatThreader::MakeThread(IThread *pThread)
|
||||
{
|
||||
ThreadParams params;
|
||||
params.flags = Thread_AutoRelease;
|
||||
MakeThread(pThread, ¶ms);
|
||||
}
|
||||
|
||||
IThreadHandle *CompatThreader::MakeThread(IThread *pThread, ThreadFlags flags)
|
||||
{
|
||||
ThreadParams params;
|
||||
params.flags = flags;
|
||||
return MakeThread(pThread, ¶ms);
|
||||
}
|
||||
|
||||
IThreadHandle *CompatThreader::MakeThread(IThread *pThread, const ThreadParams *params)
|
||||
{
|
||||
ThreadParams def_params;
|
||||
if (!params)
|
||||
params = &def_params;
|
||||
|
||||
return new CompatThread(pThread, params);
|
||||
}
|
||||
|
||||
void CompatThreader::GetPriorityBounds(ThreadPriority &max, ThreadPriority &min)
|
||||
{
|
||||
min = ThreadPrio_Normal;
|
||||
max = ThreadPrio_Normal;
|
||||
}
|
||||
|
||||
IMutex *CompatThreader::MakeMutex()
|
||||
{
|
||||
return new CompatMutex();
|
||||
}
|
||||
|
||||
void CompatThreader::ThreadSleep(unsigned int ms)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
}
|
||||
|
||||
class CompatEventSignal final : public IEventSignal
|
||||
{
|
||||
public:
|
||||
void Wait() override {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
cv_.wait(lock);
|
||||
}
|
||||
void Signal() override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
cv_.notify_all();
|
||||
}
|
||||
void DestroyThis() override {
|
||||
delete this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
};
|
||||
|
||||
IEventSignal *CompatThreader::MakeEventSignal()
|
||||
{
|
||||
return new CompatEventSignal();
|
||||
}
|
||||
|
||||
IThreadWorker *CompatThreader::MakeWorker(IThreadWorkerCallbacks *hooks, bool threaded)
|
||||
{
|
||||
if (!threaded)
|
||||
return new BaseWorker(hooks);
|
||||
return new CompatWorker(hooks);
|
||||
}
|
||||
|
||||
void CompatThreader::DestroyWorker(IThreadWorker *pWorker)
|
||||
{
|
||||
delete pWorker;
|
||||
}
|
||||
|
||||
IThreader *g_pThreader = &sCompatThreader;
|
||||
|
||||
class RegThreadStuff : public SMGlobalClass
|
||||
{
|
||||
|
@ -32,49 +32,14 @@
|
||||
#ifndef _INCLUDE_SOURCEMOD_THREAD_SUPPORT_H
|
||||
#define _INCLUDE_SOURCEMOD_THREAD_SUPPORT_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <IThreader.h>
|
||||
#include <am-thread-utils.h>
|
||||
#include <am-utility.h>
|
||||
|
||||
using namespace SourceMod;
|
||||
|
||||
class CompatMutex : public IMutex
|
||||
{
|
||||
public:
|
||||
bool TryLock() {
|
||||
return mutex_.TryLock();
|
||||
}
|
||||
void Lock() {
|
||||
mutex_.Lock();
|
||||
}
|
||||
void Unlock() {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
void DestroyThis() {
|
||||
delete this;
|
||||
}
|
||||
private:
|
||||
ke::Mutex mutex_;
|
||||
};
|
||||
|
||||
class CompatCondVar : public IEventSignal
|
||||
{
|
||||
public:
|
||||
void Wait() {
|
||||
ke::AutoLock lock(&cv_);
|
||||
cv_.Wait();
|
||||
}
|
||||
void Signal() {
|
||||
ke::AutoLock lock(&cv_);
|
||||
cv_.Notify();
|
||||
}
|
||||
void DestroyThis() {
|
||||
delete this;
|
||||
}
|
||||
private:
|
||||
ke::ConditionVariable cv_;
|
||||
};
|
||||
|
||||
extern IThreader *g_pThreader;
|
||||
|
||||
#endif //_INCLUDE_SOURCEMOD_THREAD_SUPPORT_H
|
||||
|
@ -1,199 +0,0 @@
|
||||
/**
|
||||
* vim: set ts=4 sw=4 tw=99 noet:
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include "PosixThreads.h"
|
||||
#include "ThreadWorker.h"
|
||||
|
||||
IThreadWorker *PosixThreader::MakeWorker(IThreadWorkerCallbacks *hooks, bool threaded)
|
||||
{
|
||||
if (threaded)
|
||||
{
|
||||
return new ThreadWorker(hooks, this, DEFAULT_THINK_TIME_MS);
|
||||
} else {
|
||||
return new BaseWorker(hooks);
|
||||
}
|
||||
}
|
||||
|
||||
void PosixThreader::DestroyWorker(IThreadWorker *pWorker)
|
||||
{
|
||||
delete pWorker;
|
||||
}
|
||||
|
||||
void PosixThreader::ThreadSleep(unsigned int ms)
|
||||
{
|
||||
usleep( ms * 1000 );
|
||||
}
|
||||
|
||||
void PosixThreader::GetPriorityBounds(ThreadPriority &max, ThreadPriority &min)
|
||||
{
|
||||
max = ThreadPrio_Normal;
|
||||
min = ThreadPrio_Normal;
|
||||
}
|
||||
|
||||
IMutex *PosixThreader::MakeMutex()
|
||||
{
|
||||
return new CompatMutex();
|
||||
}
|
||||
|
||||
void PosixThreader::MakeThread(IThread *pThread)
|
||||
{
|
||||
ThreadParams defparams;
|
||||
|
||||
defparams.flags = Thread_AutoRelease;
|
||||
defparams.prio = ThreadPrio_Normal;
|
||||
|
||||
MakeThread(pThread, &defparams);
|
||||
}
|
||||
|
||||
IThreadHandle *PosixThreader::MakeThread(IThread *pThread, ThreadFlags flags)
|
||||
{
|
||||
ThreadParams defparams;
|
||||
|
||||
defparams.flags = flags;
|
||||
defparams.prio = ThreadPrio_Normal;
|
||||
|
||||
return MakeThread(pThread, &defparams);
|
||||
}
|
||||
|
||||
void PosixThreader::ThreadHandle::Run()
|
||||
{
|
||||
// Wait for an unpause if necessary.
|
||||
{
|
||||
ke::AutoLock lock(&m_runlock);
|
||||
if (m_state == Thread_Paused)
|
||||
m_runlock.Wait();
|
||||
}
|
||||
|
||||
m_run->RunThread(this);
|
||||
m_state = Thread_Done;
|
||||
m_run->OnTerminate(this, false);
|
||||
|
||||
if (m_params.flags & Thread_AutoRelease)
|
||||
delete this;
|
||||
}
|
||||
|
||||
ThreadParams g_defparams;
|
||||
IThreadHandle *PosixThreader::MakeThread(IThread *pThread, const ThreadParams *params)
|
||||
{
|
||||
if (params == NULL)
|
||||
params = &g_defparams;
|
||||
|
||||
ThreadHandle* pHandle = new ThreadHandle(this, pThread, params);
|
||||
|
||||
pHandle->m_thread = new ke::Thread([pHandle]() -> void {
|
||||
pHandle->Run();
|
||||
}, "SourceMod");
|
||||
if (!pHandle->m_thread->Succeeded()) {
|
||||
delete pHandle;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!(params->flags & Thread_CreateSuspended))
|
||||
pHandle->Unpause();
|
||||
|
||||
return pHandle;
|
||||
}
|
||||
|
||||
IEventSignal *PosixThreader::MakeEventSignal()
|
||||
{
|
||||
return new CompatCondVar();
|
||||
}
|
||||
|
||||
/******************
|
||||
* Thread Handles *
|
||||
******************/
|
||||
|
||||
PosixThreader::ThreadHandle::ThreadHandle(IThreader *parent, IThread *run, const ThreadParams *params) :
|
||||
m_parent(parent), m_params(*params), m_run(run), m_state(Thread_Paused)
|
||||
{
|
||||
}
|
||||
|
||||
PosixThreader::ThreadHandle::~ThreadHandle()
|
||||
{
|
||||
}
|
||||
|
||||
bool PosixThreader::ThreadHandle::WaitForThread()
|
||||
{
|
||||
if (!m_thread)
|
||||
return false;
|
||||
|
||||
m_thread->Join();
|
||||
return true;
|
||||
}
|
||||
|
||||
ThreadState PosixThreader::ThreadHandle::GetState()
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
IThreadCreator *PosixThreader::ThreadHandle::Parent()
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
void PosixThreader::ThreadHandle::DestroyThis()
|
||||
{
|
||||
if (m_params.flags & Thread_AutoRelease)
|
||||
return;
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
void PosixThreader::ThreadHandle::GetParams(ThreadParams *ptparams)
|
||||
{
|
||||
if (!ptparams)
|
||||
return;
|
||||
|
||||
*ptparams = m_params;
|
||||
}
|
||||
|
||||
ThreadPriority PosixThreader::ThreadHandle::GetPriority()
|
||||
{
|
||||
return ThreadPrio_Normal;
|
||||
}
|
||||
|
||||
bool PosixThreader::ThreadHandle::SetPriority(ThreadPriority prio)
|
||||
{
|
||||
return (prio == ThreadPrio_Normal);
|
||||
}
|
||||
|
||||
bool PosixThreader::ThreadHandle::Unpause()
|
||||
{
|
||||
if (m_state != Thread_Paused)
|
||||
return false;
|
||||
|
||||
ke::AutoLock lock(&m_runlock);
|
||||
m_state = Thread_Running;
|
||||
m_runlock.Notify();
|
||||
return true;
|
||||
}
|
||||
|
@ -1,86 +0,0 @@
|
||||
/**
|
||||
* vim: set ts=4 sw=4 tw=99 noet:
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_POSIXTHREADS_H_
|
||||
#define _INCLUDE_POSIXTHREADS_H_
|
||||
|
||||
#include <pthread.h>
|
||||
#include <am-thread-utils.h>
|
||||
#include "IThreader.h"
|
||||
|
||||
using namespace SourceMod;
|
||||
|
||||
class PosixThreader : public IThreader
|
||||
{
|
||||
public:
|
||||
class ThreadHandle : public IThreadHandle
|
||||
{
|
||||
friend class PosixThreader;
|
||||
public:
|
||||
ThreadHandle(IThreader *parent, IThread *run, const ThreadParams *params);
|
||||
virtual ~ThreadHandle();
|
||||
public:
|
||||
virtual bool WaitForThread();
|
||||
virtual void DestroyThis();
|
||||
virtual IThreadCreator *Parent();
|
||||
virtual void GetParams(ThreadParams *ptparams);
|
||||
virtual ThreadPriority GetPriority();
|
||||
virtual bool SetPriority(ThreadPriority prio);
|
||||
virtual ThreadState GetState();
|
||||
virtual bool Unpause();
|
||||
public:
|
||||
void Run();
|
||||
protected:
|
||||
IThreader *m_parent; //Parent handle
|
||||
ThreadParams m_params; //Current Parameters
|
||||
IThread *m_run; //Runnable context
|
||||
ke::AutoPtr<ke::Thread> m_thread;
|
||||
ke::ConditionVariable m_runlock;
|
||||
ThreadState m_state; //internal state
|
||||
};
|
||||
public:
|
||||
IMutex *MakeMutex();
|
||||
void MakeThread(IThread *pThread);
|
||||
IThreadHandle *MakeThread(IThread *pThread, ThreadFlags flags);
|
||||
IThreadHandle *MakeThread(IThread *pThread, const ThreadParams *params);
|
||||
void GetPriorityBounds(ThreadPriority &max, ThreadPriority &min);
|
||||
void ThreadSleep(unsigned int ms);
|
||||
IEventSignal *MakeEventSignal();
|
||||
IThreadWorker *MakeWorker(IThreadWorkerCallbacks *hooks, bool threaded);
|
||||
void DestroyWorker(IThreadWorker *pWorker);
|
||||
};
|
||||
|
||||
#if defined SM_DEFAULT_THREADER && !defined SM_MAIN_THREADER
|
||||
#define SM_MAIN_THREADER PosixThreader;
|
||||
typedef class PosixThreader MainThreader;
|
||||
#endif
|
||||
|
||||
#endif //_INCLUDE_POSIXTHREADS_H_
|
@ -1,218 +0,0 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#include "ThreadWorker.h"
|
||||
|
||||
ThreadWorker::ThreadWorker(IThreadWorkerCallbacks *hooks) : BaseWorker(hooks),
|
||||
m_Threader(NULL),
|
||||
me(NULL),
|
||||
m_think_time(DEFAULT_THINK_TIME_MS)
|
||||
{
|
||||
m_state = Worker_Invalid;
|
||||
}
|
||||
|
||||
ThreadWorker::ThreadWorker(IThreadWorkerCallbacks *hooks, IThreader *pThreader, unsigned int thinktime) :
|
||||
BaseWorker(hooks),
|
||||
m_Threader(pThreader),
|
||||
me(NULL),
|
||||
m_think_time(thinktime)
|
||||
{
|
||||
m_state = m_Threader ? Worker_Stopped : Worker_Invalid;
|
||||
}
|
||||
|
||||
ThreadWorker::~ThreadWorker()
|
||||
{
|
||||
if (m_state != Worker_Stopped || m_state != Worker_Invalid)
|
||||
Stop(true);
|
||||
|
||||
if (m_ThreadQueue.size())
|
||||
Flush(true);
|
||||
}
|
||||
|
||||
void ThreadWorker::OnTerminate(IThreadHandle *pHandle, bool cancel)
|
||||
{
|
||||
//we don't particularly care
|
||||
return;
|
||||
}
|
||||
|
||||
void ThreadWorker::RunThread(IThreadHandle *pHandle)
|
||||
{
|
||||
if (m_pHooks)
|
||||
m_pHooks->OnWorkerStart(this);
|
||||
|
||||
ke::AutoLock lock(&monitor_);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (m_state == Worker_Paused)
|
||||
{
|
||||
// Wait until we're told to wake up.
|
||||
monitor_.Wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_state == Worker_Stopped)
|
||||
{
|
||||
// We've been told to stop entirely. If we've also been told to
|
||||
// flush the queue, do that now.
|
||||
while (!m_ThreadQueue.empty())
|
||||
{
|
||||
// Release the lock since PopThreadFromQueue() will re-acquire it. The
|
||||
// main thread is blocking anyway.
|
||||
ke::AutoUnlock unlock(&monitor_);
|
||||
RunFrame();
|
||||
}
|
||||
assert(m_state == Worker_Stopped);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(m_state == Worker_Running);
|
||||
|
||||
// Process one frame.
|
||||
WorkerState oldstate = m_state;
|
||||
{
|
||||
ke::AutoUnlock unlock(&monitor_);
|
||||
RunFrame();
|
||||
}
|
||||
|
||||
// If the state changed, loop back and process the new state.
|
||||
if (m_state != oldstate)
|
||||
continue;
|
||||
|
||||
// If the thread queue is now empty, wait for a signal. Otherwise, if
|
||||
// we're on a delay, wait for either a notification or a timeout to
|
||||
// process the next item. If the queue has items and we don't have a
|
||||
// delay, then we just loop around and keep processing.
|
||||
if (m_ThreadQueue.empty())
|
||||
monitor_.Wait();
|
||||
else if (m_think_time)
|
||||
monitor_.Wait(m_think_time);
|
||||
}
|
||||
|
||||
{
|
||||
ke::AutoUnlock unlock(&monitor_);
|
||||
if (m_pHooks)
|
||||
m_pHooks->OnWorkerStop(this);
|
||||
}
|
||||
}
|
||||
|
||||
SWThreadHandle *ThreadWorker::PopThreadFromQueue()
|
||||
{
|
||||
ke::AutoLock lock(&monitor_);
|
||||
if (m_state <= Worker_Stopped)
|
||||
return NULL;
|
||||
|
||||
return BaseWorker::PopThreadFromQueue();
|
||||
}
|
||||
|
||||
void ThreadWorker::AddThreadToQueue(SWThreadHandle *pHandle)
|
||||
{
|
||||
ke::AutoLock lock(&monitor_);
|
||||
if (m_state <= Worker_Stopped)
|
||||
return;
|
||||
|
||||
BaseWorker::AddThreadToQueue(pHandle);
|
||||
monitor_.Notify();
|
||||
}
|
||||
|
||||
WorkerState ThreadWorker::GetStatus(unsigned int *threads)
|
||||
{
|
||||
ke::AutoLock lock(&monitor_);
|
||||
return BaseWorker::GetStatus(threads);
|
||||
}
|
||||
|
||||
void ThreadWorker::SetThinkTimePerFrame(unsigned int thinktime)
|
||||
{
|
||||
m_think_time = thinktime;
|
||||
}
|
||||
|
||||
bool ThreadWorker::Start()
|
||||
{
|
||||
if (m_state == Worker_Invalid && m_Threader == NULL)
|
||||
return false;
|
||||
|
||||
if (m_state != Worker_Stopped)
|
||||
return false;
|
||||
|
||||
m_state = Worker_Running;
|
||||
|
||||
ThreadParams pt;
|
||||
pt.flags = Thread_Default;
|
||||
pt.prio = ThreadPrio_Normal;
|
||||
me = m_Threader->MakeThread(this, &pt);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThreadWorker::Stop(bool flush_cancel)
|
||||
{
|
||||
// Change the state to signal a stop, and then trigger a notify.
|
||||
{
|
||||
ke::AutoLock lock(&monitor_);
|
||||
if (m_state == Worker_Invalid || m_state == Worker_Stopped)
|
||||
return false;
|
||||
|
||||
m_state = Worker_Stopped;
|
||||
m_FlushType = flush_cancel;
|
||||
monitor_.Notify();
|
||||
}
|
||||
|
||||
me->WaitForThread();
|
||||
//destroy it
|
||||
me->DestroyThis();
|
||||
//flush all remaining events
|
||||
Flush(true);
|
||||
|
||||
me = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThreadWorker::Pause()
|
||||
{
|
||||
if (m_state != Worker_Running)
|
||||
return false;
|
||||
|
||||
ke::AutoLock lock(&monitor_);
|
||||
m_state = Worker_Paused;
|
||||
monitor_.Notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ThreadWorker::Unpause()
|
||||
{
|
||||
if (m_state != Worker_Paused)
|
||||
return false;
|
||||
|
||||
ke::AutoLock lock(&monitor_);
|
||||
m_state = Worker_Running;
|
||||
monitor_.Notify();
|
||||
return true;
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_SOURCEMOD_THREADWORKER_H
|
||||
#define _INCLUDE_SOURCEMOD_THREADWORKER_H
|
||||
|
||||
#include "BaseWorker.h"
|
||||
|
||||
#define DEFAULT_THINK_TIME_MS 20
|
||||
|
||||
class ThreadWorker : public BaseWorker, public IThread
|
||||
{
|
||||
public:
|
||||
ThreadWorker(IThreadWorkerCallbacks *hooks);
|
||||
ThreadWorker(IThreadWorkerCallbacks *hooks, IThreader *pThreader, unsigned int thinktime=DEFAULT_THINK_TIME_MS);
|
||||
virtual ~ThreadWorker();
|
||||
public: //IThread
|
||||
virtual void OnTerminate(IThreadHandle *pHandle, bool cancel);
|
||||
virtual void RunThread(IThreadHandle *pHandle);
|
||||
public: //IWorker
|
||||
//Controls the worker
|
||||
virtual bool Pause();
|
||||
virtual bool Unpause();
|
||||
virtual bool Start();
|
||||
virtual bool Stop(bool flush_cancel);
|
||||
//returns status and number of threads in queue
|
||||
virtual WorkerState GetStatus(unsigned int *numThreads);
|
||||
//virtual void SetMaxThreadsPerFrame(unsigned int threads);
|
||||
virtual void SetThinkTimePerFrame(unsigned int thinktime);
|
||||
public: //BaseWorker
|
||||
virtual void AddThreadToQueue(SWThreadHandle *pHandle);
|
||||
virtual SWThreadHandle *PopThreadFromQueue();
|
||||
protected:
|
||||
IThreader *m_Threader;
|
||||
IThreadHandle *me;
|
||||
unsigned int m_think_time;
|
||||
bool m_FlushType;
|
||||
ke::ConditionVariable monitor_;
|
||||
};
|
||||
|
||||
#endif //_INCLUDE_SOURCEMOD_THREADWORKER_H
|
@ -1,216 +0,0 @@
|
||||
/**
|
||||
* vim: set ts=4 sw=4 tw=99 noet:
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#define _WIN32_WINNT 0x0400
|
||||
#include "WinThreads.h"
|
||||
#include "ThreadWorker.h"
|
||||
|
||||
IThreadWorker *WinThreader::MakeWorker(IThreadWorkerCallbacks *hooks, bool threaded)
|
||||
{
|
||||
if (threaded)
|
||||
{
|
||||
return new ThreadWorker(hooks, this, DEFAULT_THINK_TIME_MS);
|
||||
} else {
|
||||
return new BaseWorker(hooks);
|
||||
}
|
||||
}
|
||||
|
||||
void WinThreader::DestroyWorker(IThreadWorker *pWorker)
|
||||
{
|
||||
delete pWorker;
|
||||
}
|
||||
|
||||
void WinThreader::ThreadSleep(unsigned int ms)
|
||||
{
|
||||
Sleep((DWORD)ms);
|
||||
}
|
||||
|
||||
IMutex *WinThreader::MakeMutex()
|
||||
{
|
||||
return new CompatMutex();
|
||||
}
|
||||
|
||||
IThreadHandle *WinThreader::MakeThread(IThread *pThread, ThreadFlags flags)
|
||||
{
|
||||
ThreadParams defparams;
|
||||
|
||||
defparams.flags = flags;
|
||||
defparams.prio = ThreadPrio_Normal;
|
||||
|
||||
return MakeThread(pThread, &defparams);
|
||||
}
|
||||
|
||||
void WinThreader::MakeThread(IThread *pThread)
|
||||
{
|
||||
ThreadParams defparams;
|
||||
|
||||
defparams.flags = Thread_AutoRelease;
|
||||
defparams.prio = ThreadPrio_Normal;
|
||||
|
||||
MakeThread(pThread, &defparams);
|
||||
}
|
||||
|
||||
void WinThreader::ThreadHandle::Run()
|
||||
{
|
||||
// Wait for an unpause if necessary.
|
||||
{
|
||||
ke::AutoLock lock(&suspend_);
|
||||
if (m_state == Thread_Paused)
|
||||
suspend_.Wait();
|
||||
}
|
||||
|
||||
m_run->RunThread(this);
|
||||
m_state = Thread_Done;
|
||||
m_run->OnTerminate(this, false);
|
||||
if (m_params.flags & Thread_AutoRelease)
|
||||
delete this;
|
||||
}
|
||||
|
||||
void WinThreader::GetPriorityBounds(ThreadPriority &max, ThreadPriority &min)
|
||||
{
|
||||
max = ThreadPrio_Maximum;
|
||||
min = ThreadPrio_Minimum;
|
||||
}
|
||||
|
||||
ThreadParams g_defparams;
|
||||
IThreadHandle *WinThreader::MakeThread(IThread *pThread, const ThreadParams *params)
|
||||
{
|
||||
if (params == NULL)
|
||||
params = &g_defparams;
|
||||
|
||||
ThreadHandle* pHandle = new ThreadHandle(this, pThread, params);
|
||||
|
||||
pHandle->m_thread = new ke::Thread([pHandle]() -> void {
|
||||
pHandle->Run();
|
||||
}, "SourceMod");
|
||||
if (!pHandle->m_thread->Succeeded()) {
|
||||
delete pHandle;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pHandle->m_params.prio != ThreadPrio_Normal)
|
||||
pHandle->SetPriority(pHandle->m_params.prio);
|
||||
|
||||
if (!(params->flags & Thread_CreateSuspended))
|
||||
pHandle->Unpause();
|
||||
|
||||
return pHandle;
|
||||
}
|
||||
|
||||
IEventSignal *WinThreader::MakeEventSignal()
|
||||
{
|
||||
return new CompatCondVar();
|
||||
}
|
||||
|
||||
/******************
|
||||
* Thread Handles *
|
||||
******************/
|
||||
|
||||
WinThreader::ThreadHandle::ThreadHandle(IThreader *parent, IThread *run, const ThreadParams *params) :
|
||||
m_parent(parent), m_run(run), m_params(*params),
|
||||
m_state(Thread_Paused)
|
||||
{
|
||||
}
|
||||
|
||||
WinThreader::ThreadHandle::~ThreadHandle()
|
||||
{
|
||||
}
|
||||
|
||||
bool WinThreader::ThreadHandle::WaitForThread()
|
||||
{
|
||||
if (!m_thread)
|
||||
return false;
|
||||
|
||||
m_thread->Join();
|
||||
return true;
|
||||
}
|
||||
|
||||
ThreadState WinThreader::ThreadHandle::GetState()
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
IThreadCreator *WinThreader::ThreadHandle::Parent()
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
void WinThreader::ThreadHandle::DestroyThis()
|
||||
{
|
||||
if (m_params.flags & Thread_AutoRelease)
|
||||
return;
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
void WinThreader::ThreadHandle::GetParams(ThreadParams *ptparams)
|
||||
{
|
||||
if (!ptparams)
|
||||
return;
|
||||
|
||||
*ptparams = m_params;
|
||||
}
|
||||
|
||||
ThreadPriority WinThreader::ThreadHandle::GetPriority()
|
||||
{
|
||||
return m_params.prio;
|
||||
}
|
||||
|
||||
bool WinThreader::ThreadHandle::SetPriority(ThreadPriority prio)
|
||||
{
|
||||
BOOL res = FALSE;
|
||||
|
||||
if (prio >= ThreadPrio_Maximum)
|
||||
res = SetThreadPriority(m_thread->handle(), THREAD_PRIORITY_HIGHEST);
|
||||
else if (prio <= ThreadPrio_Minimum)
|
||||
res = SetThreadPriority(m_thread->handle(), THREAD_PRIORITY_LOWEST);
|
||||
else if (prio == ThreadPrio_Normal)
|
||||
res = SetThreadPriority(m_thread->handle(), THREAD_PRIORITY_NORMAL);
|
||||
else if (prio == ThreadPrio_High)
|
||||
res = SetThreadPriority(m_thread->handle(), THREAD_PRIORITY_ABOVE_NORMAL);
|
||||
else if (prio == ThreadPrio_Low)
|
||||
res = SetThreadPriority(m_thread->handle(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
m_params.prio = prio;
|
||||
|
||||
return (res != FALSE);
|
||||
}
|
||||
|
||||
bool WinThreader::ThreadHandle::Unpause()
|
||||
{
|
||||
if (m_state != Thread_Paused)
|
||||
return false;
|
||||
|
||||
ke::AutoLock lock(&suspend_);
|
||||
m_state = Thread_Running;
|
||||
suspend_.Notify();
|
||||
return true;
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||
* =============================================================================
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, version 3.0, as published by the
|
||||
* Free Software Foundation.
|
||||
*
|
||||
* This program 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
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, AlliedModders LLC gives you permission to link the
|
||||
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
||||
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
||||
* by the Valve Corporation. You must obey the GNU General Public License in
|
||||
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
||||
* this exception to all derivative works. AlliedModders LLC defines further
|
||||
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
||||
* or <http://www.sourcemod.net/license.php>.
|
||||
*
|
||||
* Version: $Id$
|
||||
*/
|
||||
|
||||
#ifndef _INCLUDE_WINTHREADS_H_
|
||||
#define _INCLUDE_WINTHREADS_H_
|
||||
|
||||
#include <am-thread-utils.h>
|
||||
#include <am-utility.h>
|
||||
#include <windows.h>
|
||||
#include "IThreader.h"
|
||||
|
||||
using namespace SourceMod;
|
||||
|
||||
class WinThreader : public IThreader
|
||||
{
|
||||
public:
|
||||
class ThreadHandle : public IThreadHandle
|
||||
{
|
||||
friend class WinThreader;
|
||||
friend DWORD WINAPI Win32_ThreadGate(LPVOID param);
|
||||
public:
|
||||
ThreadHandle(IThreader *parent, IThread *run, const ThreadParams *params);
|
||||
virtual ~ThreadHandle();
|
||||
public:
|
||||
virtual bool WaitForThread();
|
||||
virtual void DestroyThis();
|
||||
virtual IThreadCreator *Parent();
|
||||
virtual void GetParams(ThreadParams *ptparams);
|
||||
virtual ThreadPriority GetPriority();
|
||||
virtual bool SetPriority(ThreadPriority prio);
|
||||
virtual ThreadState GetState();
|
||||
virtual bool Unpause();
|
||||
virtual void Run();
|
||||
protected:
|
||||
IThreader *m_parent; //Parent handle
|
||||
ke::AutoPtr<ke::Thread> m_thread;
|
||||
ThreadParams m_params; //Current Parameters
|
||||
IThread *m_run; //Runnable context
|
||||
ThreadState m_state; //internal state
|
||||
ke::ConditionVariable suspend_;
|
||||
};
|
||||
|
||||
public:
|
||||
IMutex *MakeMutex();
|
||||
void MakeThread(IThread *pThread);
|
||||
IThreadHandle *MakeThread(IThread *pThread, ThreadFlags flags);
|
||||
IThreadHandle *MakeThread(IThread *pThread, const ThreadParams *params);
|
||||
void GetPriorityBounds(ThreadPriority &max, ThreadPriority &min);
|
||||
void ThreadSleep(unsigned int ms);
|
||||
IEventSignal *MakeEventSignal();
|
||||
IThreadWorker *MakeWorker(IThreadWorkerCallbacks *hooks, bool threaded);
|
||||
void DestroyWorker(IThreadWorker *pWorker);
|
||||
};
|
||||
|
||||
#if defined SM_DEFAULT_THREADER && !defined SM_MAIN_THREADER
|
||||
#define SM_MAIN_THREADER WinThreader;
|
||||
typedef class WinThreader MainThreader;
|
||||
#endif
|
||||
|
||||
#endif //_INCLUDE_WINTHREADS_H_
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* vim: set ts=4 :
|
||||
* vim: set ts=4 sw=4 tw=99 noet :
|
||||
* =============================================================================
|
||||
* SourceMod
|
||||
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
||||
@ -273,7 +273,9 @@ namespace SourceMod
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describes a simple "condition variable"/signal lock.
|
||||
* @brief Object that can be used to signal from one thread to another.
|
||||
* This should not be used and is deprecated. Use C++11
|
||||
* std::condition_variable instead, as this version is fundamentally racy.
|
||||
*/
|
||||
class IEventSignal
|
||||
{
|
||||
@ -326,7 +328,7 @@ namespace SourceMod
|
||||
* @return Number of tasks processed.
|
||||
*/
|
||||
virtual unsigned int RunFrame() =0;
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Pauses the worker.
|
||||
*
|
||||
@ -446,9 +448,9 @@ namespace SourceMod
|
||||
virtual void ThreadSleep(unsigned int ms) =0;
|
||||
|
||||
/**
|
||||
* @brief Creates a non-signalled event.
|
||||
* @brief Deprecated; do not use.
|
||||
*
|
||||
* @return A new IEventSignal pointer (must be destroyed).
|
||||
* @return Returns a new IEventSignal.
|
||||
*/
|
||||
virtual IEventSignal *MakeEventSignal() =0;
|
||||
|
||||
|
2
tools/checkout-deps.sh
vendored
2
tools/checkout-deps.sh
vendored
@ -136,7 +136,7 @@ if [ $? -eq 1 ]; then
|
||||
checkout
|
||||
|
||||
cd ambuild
|
||||
if [ $iswin -eq 1 ]; then
|
||||
if [ $iswin -eq 1 ] || [ $ismac -eq 1 ]; then
|
||||
python setup.py install
|
||||
else
|
||||
python setup.py build
|
||||
|
Loading…
Reference in New Issue
Block a user