2021-04-12 18:25:02 +00:00
|
|
|
/*
|
|
|
|
AngelCode Scripting Library
|
|
|
|
Copyright (c) 2003-2014 Andreas Jonsson
|
|
|
|
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
|
|
warranty. In no event will the authors be held liable for any
|
|
|
|
damages arising from the use of this software.
|
|
|
|
|
|
|
|
Permission is granted to anyone to use this software for any
|
|
|
|
purpose, including commercial applications, and to alter it and
|
|
|
|
redistribute it freely, subject to the following restrictions:
|
|
|
|
|
|
|
|
1. The origin of this software must not be misrepresented; you
|
|
|
|
must not claim that you wrote the original software. If you use
|
|
|
|
this software in a product, an acknowledgment in the product
|
|
|
|
documentation would be appreciated but is not required.
|
|
|
|
|
|
|
|
2. Altered source versions must be plainly marked as such, and
|
|
|
|
must not be misrepresented as being the original software.
|
|
|
|
|
|
|
|
3. This notice may not be removed or altered from any source
|
|
|
|
distribution.
|
|
|
|
|
|
|
|
The original version of this library can be located at:
|
|
|
|
http://www.angelcode.com/angelscript/
|
|
|
|
|
|
|
|
Andreas Jonsson
|
|
|
|
andreas@angelcode.com
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// as_thread.cpp
|
|
|
|
//
|
|
|
|
// Functions for multi threading support
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "as_config.h"
|
|
|
|
#include "as_thread.h"
|
|
|
|
#include "as_atomic.h"
|
|
|
|
|
|
|
|
BEGIN_AS_NAMESPACE
|
|
|
|
|
|
|
|
//=======================================================================
|
|
|
|
|
|
|
|
// Singleton
|
|
|
|
static asCThreadManager *threadManager = 0;
|
|
|
|
|
|
|
|
//======================================================================
|
|
|
|
|
|
|
|
// Global API functions
|
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
|
|
|
|
AS_API int asThreadCleanup()
|
|
|
|
{
|
|
|
|
return asCThreadManager::CleanupLocalData();
|
|
|
|
}
|
|
|
|
|
|
|
|
AS_API asIThreadManager *asGetThreadManager()
|
|
|
|
{
|
|
|
|
return threadManager;
|
|
|
|
}
|
|
|
|
|
|
|
|
AS_API int asPrepareMultithread(asIThreadManager *externalThreadMgr)
|
|
|
|
{
|
|
|
|
return asCThreadManager::Prepare(externalThreadMgr);
|
|
|
|
}
|
|
|
|
|
|
|
|
AS_API void asUnprepareMultithread()
|
|
|
|
{
|
|
|
|
asCThreadManager::Unprepare();
|
|
|
|
}
|
|
|
|
|
|
|
|
AS_API void asAcquireExclusiveLock()
|
|
|
|
{
|
|
|
|
if( threadManager )
|
|
|
|
{
|
|
|
|
ACQUIREEXCLUSIVE(threadManager->appRWLock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AS_API void asReleaseExclusiveLock()
|
|
|
|
{
|
|
|
|
if( threadManager )
|
|
|
|
{
|
|
|
|
RELEASEEXCLUSIVE(threadManager->appRWLock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AS_API void asAcquireSharedLock()
|
|
|
|
{
|
|
|
|
if( threadManager )
|
|
|
|
{
|
|
|
|
ACQUIRESHARED(threadManager->appRWLock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AS_API void asReleaseSharedLock()
|
|
|
|
{
|
|
|
|
if( threadManager )
|
|
|
|
{
|
|
|
|
RELEASESHARED(threadManager->appRWLock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//======================================================================
|
|
|
|
|
|
|
|
#if !defined(AS_NO_THREADS) && defined(_MSC_VER) && defined(AS_WINDOWS_THREADS) && (WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
__declspec(thread) asCThreadLocalData *asCThreadManager::tld = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
asCThreadManager::asCThreadManager()
|
|
|
|
{
|
|
|
|
// We're already in the critical section when this function is called
|
|
|
|
|
|
|
|
#ifdef AS_NO_THREADS
|
|
|
|
tld = 0;
|
|
|
|
#else
|
|
|
|
// Allocate the thread local storage
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_key_t pKey;
|
|
|
|
pthread_key_create(&pKey, 0);
|
|
|
|
tlsKey = (asDWORD)pKey;
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
#if defined(_MSC_VER) && (WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
tld = 0;
|
|
|
|
#else
|
|
|
|
tlsKey = (asDWORD)TlsAlloc();
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
refCount = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int asCThreadManager::Prepare(asIThreadManager *externalThreadMgr)
|
|
|
|
{
|
|
|
|
// Don't allow an external thread manager if there
|
|
|
|
// is already a thread manager defined
|
|
|
|
if( externalThreadMgr && threadManager )
|
|
|
|
return asINVALID_ARG;
|
|
|
|
|
|
|
|
// The critical section cannot be declared globally, as there is no
|
|
|
|
// guarantee for the order in which global variables are initialized
|
|
|
|
// or uninitialized.
|
|
|
|
|
|
|
|
// For this reason it's not possible to prevent two threads from calling
|
|
|
|
// AddRef at the same time, so there is a chance for a race condition here.
|
|
|
|
|
|
|
|
// To avoid the race condition when the thread manager is first created,
|
|
|
|
// the application must make sure to call the global asPrepareForMultiThread()
|
|
|
|
// in the main thread before any other thread creates a script engine.
|
|
|
|
if( threadManager == 0 && externalThreadMgr == 0 )
|
|
|
|
threadManager = asNEW(asCThreadManager);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If an application uses different dlls each dll will get it's own memory
|
|
|
|
// space for global variables. If multiple dlls then uses AngelScript's
|
|
|
|
// global thread support functions it is then best to share the thread
|
|
|
|
// manager to make sure all dlls use the same critical section.
|
|
|
|
if( externalThreadMgr )
|
|
|
|
threadManager = reinterpret_cast<asCThreadManager*>(externalThreadMgr);
|
|
|
|
|
|
|
|
ENTERCRITICALSECTION(threadManager->criticalSection);
|
|
|
|
threadManager->refCount++;
|
|
|
|
LEAVECRITICALSECTION(threadManager->criticalSection);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Success
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void asCThreadManager::Unprepare()
|
|
|
|
{
|
|
|
|
asASSERT(threadManager);
|
|
|
|
|
|
|
|
if( threadManager == 0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// It's necessary to protect this section so no
|
|
|
|
// other thread attempts to call AddRef or Release
|
|
|
|
// while clean up is in progress.
|
|
|
|
ENTERCRITICALSECTION(threadManager->criticalSection);
|
|
|
|
if( --threadManager->refCount == 0 )
|
|
|
|
{
|
|
|
|
// Make sure the local data is destroyed, at least for the current thread
|
|
|
|
CleanupLocalData();
|
|
|
|
|
|
|
|
// As the critical section will be destroyed together
|
|
|
|
// with the thread manager we must first clear the global
|
|
|
|
// variable in case a new thread manager needs to be created;
|
|
|
|
asCThreadManager *mgr = threadManager;
|
|
|
|
threadManager = 0;
|
|
|
|
|
|
|
|
// Leave the critical section before it is destroyed
|
|
|
|
LEAVECRITICALSECTION(mgr->criticalSection);
|
|
|
|
|
|
|
|
asDELETE(mgr,asCThreadManager);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
LEAVECRITICALSECTION(threadManager->criticalSection);
|
|
|
|
}
|
|
|
|
|
|
|
|
asCThreadManager::~asCThreadManager()
|
|
|
|
{
|
|
|
|
#ifndef AS_NO_THREADS
|
|
|
|
// Deallocate the thread local storage
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_key_delete((pthread_key_t)tlsKey);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
#if defined(_MSC_VER) && (WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
tld = 0;
|
|
|
|
#else
|
|
|
|
TlsFree((DWORD)tlsKey);
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#else
|
|
|
|
if( tld )
|
|
|
|
{
|
|
|
|
asDELETE(tld,asCThreadLocalData);
|
|
|
|
}
|
|
|
|
tld = 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-02-05 16:32:56 +00:00
|
|
|
#if defined(AS_STD_THREADS)
|
|
|
|
thread_local asCThreadLocalData *std_tld;
|
|
|
|
#endif
|
|
|
|
|
2021-04-12 18:25:02 +00:00
|
|
|
int asCThreadManager::CleanupLocalData()
|
|
|
|
{
|
|
|
|
if( threadManager == 0 )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#ifndef AS_NO_THREADS
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
asCThreadLocalData *tld = (asCThreadLocalData*)pthread_getspecific((pthread_key_t)threadManager->tlsKey);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
#if !defined(_MSC_VER) || !(WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
asCThreadLocalData *tld = (asCThreadLocalData*)TlsGetValue((DWORD)threadManager->tlsKey);
|
2022-02-05 16:32:56 +00:00
|
|
|
#endif
|
|
|
|
#elif defined(AS_STD_THREADS)
|
|
|
|
auto tld = std_tld;
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if( tld == 0 )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if( tld->activeContexts.GetLength() == 0 )
|
|
|
|
{
|
|
|
|
asDELETE(tld,asCThreadLocalData);
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_setspecific((pthread_key_t)threadManager->tlsKey, 0);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
#if defined(_MSC_VER) && (WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
tld = 0;
|
|
|
|
#else
|
|
|
|
TlsSetValue((DWORD)threadManager->tlsKey, 0);
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return asCONTEXT_ACTIVE;
|
|
|
|
|
|
|
|
#else
|
|
|
|
if( threadManager->tld )
|
|
|
|
{
|
|
|
|
if( threadManager->tld->activeContexts.GetLength() == 0 )
|
|
|
|
{
|
|
|
|
asDELETE(threadManager->tld,asCThreadLocalData);
|
|
|
|
threadManager->tld = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return asCONTEXT_ACTIVE;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
asCThreadLocalData *asCThreadManager::GetLocalData()
|
|
|
|
{
|
|
|
|
if( threadManager == 0 )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#ifndef AS_NO_THREADS
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
asCThreadLocalData *tld = (asCThreadLocalData*)pthread_getspecific((pthread_key_t)threadManager->tlsKey);
|
|
|
|
if( tld == 0 )
|
|
|
|
{
|
|
|
|
tld = asNEW(asCThreadLocalData)();
|
|
|
|
pthread_setspecific((pthread_key_t)threadManager->tlsKey, tld);
|
|
|
|
}
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
#if defined(_MSC_VER) && (WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
if( tld == 0 )
|
|
|
|
tld = asNEW(asCThreadLocalData)();
|
|
|
|
#else
|
|
|
|
asCThreadLocalData *tld = (asCThreadLocalData*)TlsGetValue((DWORD)threadManager->tlsKey);
|
|
|
|
if( tld == 0 )
|
|
|
|
{
|
|
|
|
tld = asNEW(asCThreadLocalData)();
|
|
|
|
TlsSetValue((DWORD)threadManager->tlsKey, tld);
|
|
|
|
}
|
2022-02-05 16:32:56 +00:00
|
|
|
#endif
|
|
|
|
#elif defined(AS_STD_THREADS)
|
|
|
|
if(std_tld == nullptr)
|
|
|
|
{
|
|
|
|
std_tld = asNEW(asCThreadLocalData)();
|
|
|
|
}
|
|
|
|
auto tld = std_tld;
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return tld;
|
|
|
|
#else
|
|
|
|
if( threadManager->tld == 0 )
|
|
|
|
threadManager->tld = asNEW(asCThreadLocalData)();
|
|
|
|
|
|
|
|
return threadManager->tld;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================================
|
|
|
|
|
|
|
|
asCThreadLocalData::asCThreadLocalData()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
asCThreadLocalData::~asCThreadLocalData()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================================
|
|
|
|
|
|
|
|
#ifndef AS_NO_THREADS
|
|
|
|
asCThreadCriticalSection::asCThreadCriticalSection()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_mutex_init(&cs, 0);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
#if defined(_MSC_VER) && (WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
// Only the Ex version is available on Windows Store
|
|
|
|
InitializeCriticalSectionEx(&cs, 4000, 0);
|
|
|
|
#else
|
|
|
|
// Only the non-Ex version is available on WinXP and older
|
|
|
|
// MinGW also only defines this version
|
|
|
|
InitializeCriticalSection(&cs);
|
|
|
|
#endif
|
2022-02-11 12:15:08 +00:00
|
|
|
#elif defined AS_STD_THREADS
|
|
|
|
_mutex = std::make_unique<std::mutex>();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
asCThreadCriticalSection::~asCThreadCriticalSection()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_mutex_destroy(&cs);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
DeleteCriticalSection(&cs);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void asCThreadCriticalSection::Enter()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_mutex_lock(&cs);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
EnterCriticalSection(&cs);
|
2022-02-05 16:32:56 +00:00
|
|
|
#elif defined(AS_STD_THREADS)
|
2022-02-11 12:15:08 +00:00
|
|
|
_mutex->lock();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void asCThreadCriticalSection::Leave()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_mutex_unlock(&cs);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
LeaveCriticalSection(&cs);
|
2022-02-05 16:32:56 +00:00
|
|
|
#elif defined(AS_STD_THREADS)
|
2022-02-11 12:15:08 +00:00
|
|
|
_mutex->unlock();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool asCThreadCriticalSection::TryEnter()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
return !pthread_mutex_trylock(&cs);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
return TryEnterCriticalSection(&cs) ? true : false;
|
2022-02-05 16:32:56 +00:00
|
|
|
#elif defined(AS_STD_THREADS)
|
2022-02-11 12:15:08 +00:00
|
|
|
return _mutex->try_lock();
|
2021-04-12 18:25:02 +00:00
|
|
|
#else
|
|
|
|
return true;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
asCThreadReadWriteLock::asCThreadReadWriteLock()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
int r = pthread_rwlock_init(&lock, 0);
|
|
|
|
asASSERT( r == 0 );
|
|
|
|
UNUSED_VAR(r);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
#if defined(_MSC_VER) && (WINAPI_FAMILY & WINAPI_FAMILY_PHONE_APP)
|
|
|
|
// Only the Ex versions are available on Windows Store
|
|
|
|
|
|
|
|
// Create a semaphore to allow up to maxReaders simultaneous readers
|
|
|
|
readLocks = CreateSemaphoreExW(NULL, maxReaders, maxReaders, 0, 0, 0);
|
|
|
|
// Create a critical section to synchronize writers
|
|
|
|
InitializeCriticalSectionEx(&writeLock, 4000, 0);
|
|
|
|
#else
|
|
|
|
readLocks = CreateSemaphoreW(NULL, maxReaders, maxReaders, 0);
|
|
|
|
InitializeCriticalSection(&writeLock);
|
|
|
|
#endif
|
2022-02-11 12:15:08 +00:00
|
|
|
#elif defined AS_STD_THREADS
|
|
|
|
_mutex = std::make_unique<std::shared_mutex>();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
asCThreadReadWriteLock::~asCThreadReadWriteLock()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_rwlock_destroy(&lock);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
DeleteCriticalSection(&writeLock);
|
|
|
|
CloseHandle(readLocks);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void asCThreadReadWriteLock::AcquireExclusive()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_rwlock_wrlock(&lock);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
// Synchronize writers, so only one tries to lock out the readers
|
|
|
|
EnterCriticalSection(&writeLock);
|
|
|
|
|
|
|
|
// Lock all reader out from the semaphore. Do this one by one,
|
|
|
|
// so the lock doesn't have to wait until there are no readers at all.
|
|
|
|
// If we try to lock all at once it is quite possible the writer will
|
|
|
|
// never succeed.
|
|
|
|
for( asUINT n = 0; n < maxReaders; n++ )
|
|
|
|
WaitForSingleObjectEx(readLocks, INFINITE, FALSE);
|
|
|
|
|
|
|
|
// Allow another writer to lock. It will only be able to
|
|
|
|
// lock the readers when this writer releases them anyway.
|
|
|
|
LeaveCriticalSection(&writeLock);
|
2022-02-05 16:32:56 +00:00
|
|
|
#elif defined(AS_STD_THREADS)
|
2022-02-11 12:15:08 +00:00
|
|
|
_mutex->lock();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void asCThreadReadWriteLock::ReleaseExclusive()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_rwlock_unlock(&lock);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
// Release all readers at once
|
|
|
|
ReleaseSemaphore(readLocks, maxReaders, 0);
|
2022-02-05 16:32:56 +00:00
|
|
|
#elif defined(AS_STD_THREADS)
|
2022-02-11 12:15:08 +00:00
|
|
|
_mutex->unlock();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void asCThreadReadWriteLock::AcquireShared()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_rwlock_rdlock(&lock);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
// Lock a reader slot
|
|
|
|
WaitForSingleObjectEx(readLocks, INFINITE, FALSE);
|
2022-02-05 16:32:56 +00:00
|
|
|
#elif defined(AS_STD_THREADS)
|
2022-02-11 12:15:08 +00:00
|
|
|
_mutex->lock_shared();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void asCThreadReadWriteLock::ReleaseShared()
|
|
|
|
{
|
|
|
|
#if defined AS_POSIX_THREADS
|
|
|
|
pthread_rwlock_unlock(&lock);
|
|
|
|
#elif defined AS_WINDOWS_THREADS
|
|
|
|
// Release the reader slot
|
|
|
|
ReleaseSemaphore(readLocks, 1, 0);
|
2022-02-05 16:32:56 +00:00
|
|
|
#elif defined(AS_STD_THREADS)
|
2022-02-11 12:15:08 +00:00
|
|
|
_mutex->unlock_shared();
|
2021-04-12 18:25:02 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//========================================================================
|
|
|
|
|
|
|
|
END_AS_NAMESPACE
|
|
|
|
|