First
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/EACType.h>
|
||||
#include <EAAssert/eaassert.h>
|
||||
|
||||
|
||||
// Here we define character types in a simplistic way for the first
|
||||
// 256 characters of the Unicode standard. We assume that char16_t refers
|
||||
// to a wide unicode character of at least 8 bits (and usually 16 or 32 bits).
|
||||
|
||||
#define wctrl EASTDC_WCTYPE_CONTROL_1
|
||||
#define wmotn EASTDC_WCTYPE_MOTION
|
||||
#define wspac EASTDC_WCTYPE_SPACE_1
|
||||
#define wpunc EASTDC_WCTYPE_PUNCT
|
||||
#define wdigi EASTDC_WCTYPE_DIGIT
|
||||
#define whexd EASTDC_WCTYPE_XDIGIT
|
||||
#define wlowc EASTDC_WCTYPE_LOWER
|
||||
#define wuppc EASTDC_WCTYPE_UPPER
|
||||
#define wdhex (EASTDC_WCTYPE_XDIGIT | EASTDC_WCTYPE_DIGIT)
|
||||
#define wlhex (EASTDC_WCTYPE_XDIGIT | EASTDC_WCTYPE_LOWER)
|
||||
#define wuhex (EASTDC_WCTYPE_XDIGIT | EASTDC_WCTYPE_UPPER)
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
|
||||
EASTDC_API uint8_t EASTDC_WCTYPE_MAP[256] =
|
||||
{
|
||||
wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wmotn, wmotn, wmotn, wmotn, wmotn, wctrl, wctrl,
|
||||
wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl,
|
||||
wspac, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc,
|
||||
wdhex, wdhex, wdhex, wdhex, wdhex, wdhex, wdhex, wdhex, wdhex, wdhex, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc,
|
||||
wpunc, wuhex, wuhex, wuhex, wuhex, wuhex, wuhex, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc,
|
||||
wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wpunc, wpunc, wpunc, wpunc, wpunc,
|
||||
wpunc, wlhex, wlhex, wlhex, wlhex, wlhex, wlhex, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc,
|
||||
wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wpunc, wpunc, wpunc, wpunc, wctrl,
|
||||
wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl,
|
||||
wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl, wctrl,
|
||||
wspac, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wctrl, wpunc, wpunc,
|
||||
wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc, wpunc,
|
||||
wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc,
|
||||
wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc, wuppc,
|
||||
wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc,
|
||||
wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wpunc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc, wlowc
|
||||
};
|
||||
|
||||
|
||||
EASTDC_API uint8_t EASTDC_WLOWER_MAP[256] =
|
||||
{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
|
||||
'@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '[', '\\', ']', '^', '_',
|
||||
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 0x7F,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
||||
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
||||
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
|
||||
};
|
||||
|
||||
|
||||
EASTDC_API uint8_t EASTDC_WUPPER_MAP[256] =
|
||||
{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
|
||||
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
|
||||
'`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', 0x7F,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
||||
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
||||
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
|
||||
};
|
||||
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
|
||||
|
||||
#undef wctrl
|
||||
#undef wmotn
|
||||
#undef wspac
|
||||
#undef wpunc
|
||||
#undef wdigi
|
||||
#undef whexd
|
||||
#undef wlowc
|
||||
#undef wuppc
|
||||
#undef wdhex
|
||||
#undef wlhex
|
||||
#undef wuhex
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,874 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// To do: Deal with possible int64_t rollover in various parts of code below.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/EACallback.h>
|
||||
#include <EAStdC/EARandomDistribution.h>
|
||||
#include <string.h>
|
||||
#include <EAAssert/eaassert.h>
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Some stuff used to support the callbacks
|
||||
//
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
#define EA_CALLBACK_PROCESSOR_MUTEX_LOCK() mMutex.Lock()
|
||||
#define EA_CALLBACK_PROCESSOR_MUTEX_UNLOCK() mMutex.Unlock()
|
||||
#else
|
||||
#define EA_CALLBACK_PROCESSOR_MUTEX_LOCK()
|
||||
#define EA_CALLBACK_PROCESSOR_MUTEX_UNLOCK()
|
||||
#endif
|
||||
|
||||
|
||||
// We define the following macros from CoreAllocator here to avoid a dependency on the MemoryMan package.
|
||||
// #ifndef EA_CB_CA_NEW
|
||||
// #define EA_CB_CA_NEW(Class, pAllocator, pName) new ((pAllocator)->Alloc(sizeof(Class), pName, 0, EA_ALIGN_OF(Class), 0)) Class
|
||||
// #endif
|
||||
//
|
||||
// #ifndef EA_CB_CA_DELETE
|
||||
// #define EA_CB_CA_DELETE(pObject, pAllocator) EA::StdC::delete_object(pObject, pAllocator)
|
||||
// #endif
|
||||
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The use of standard min/max leads to compile errors sensitive
|
||||
// to the state of EASTDC_THREADING_SUPPORTED.
|
||||
template <class T>
|
||||
const T smin(const T& a, const T& b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
|
||||
static void DefaultCallback(Callback* pCallback, void*, uint64_t, uint64_t)
|
||||
{
|
||||
pCallback->Stop();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
/// delete_object
|
||||
///
|
||||
/// Deletes an object created by create_object.
|
||||
/// See create_object for specifications and examples.
|
||||
///
|
||||
template <typename T>
|
||||
inline void delete_object(T* pObject, Allocator::ICoreAllocator* pAllocator)
|
||||
{
|
||||
if(pObject) // As per the C++ standard, deletion of NULL results in a no-op.
|
||||
{
|
||||
pObject->~T();
|
||||
pAllocator->Free(pObject);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Callback
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Callback::Callback()
|
||||
: mPeriod(UINT64_C(1000000000))
|
||||
, mPrecision(500000)
|
||||
, mpCallbackManager(NULL)
|
||||
, mpFunction(NULL)
|
||||
, mpFunctionArg(NULL)
|
||||
, mType(kTypeTime)
|
||||
, mbStarted(0)
|
||||
, mbOneShot(false)
|
||||
, mbEnableRefCount(false)
|
||||
, mNextCallbackEvent(0)
|
||||
, mLastCallbackEvent(0)
|
||||
{
|
||||
EA_ASSERT((int64_t)mPeriod > 0); // Sanity checks.
|
||||
EA_ASSERT((int64_t)mPrecision > 0);
|
||||
|
||||
SetFunctionInfo(NULL, NULL, false);
|
||||
}
|
||||
|
||||
|
||||
Callback::Callback(CallbackFunctionType pCallbackFunc, void* pCallbackFuncArg, uint64_t period,
|
||||
uint64_t precision, Type type, bool bEnableRefCount)
|
||||
: mPeriod(period)
|
||||
, mPrecision(precision)
|
||||
, mpCallbackManager(NULL)
|
||||
, mpFunction(NULL)
|
||||
, mpFunctionArg(NULL)
|
||||
, mType(type)
|
||||
, mbStarted(0)
|
||||
, mbOneShot(false)
|
||||
, mbEnableRefCount(false)
|
||||
, mNextCallbackEvent(0)
|
||||
, mLastCallbackEvent(0)
|
||||
{
|
||||
EA_ASSERT((int64_t)mPeriod > 0); // Sanity checks.
|
||||
EA_ASSERT((int64_t)mPrecision > 0);
|
||||
|
||||
SetFunctionInfo(pCallbackFunc, pCallbackFuncArg, bEnableRefCount);
|
||||
}
|
||||
|
||||
|
||||
Callback::~Callback()
|
||||
{
|
||||
if((int32_t)mbStarted) // Cast to int32_t because mbStarted might be an atomic int class.
|
||||
Stop();
|
||||
}
|
||||
|
||||
|
||||
// Sets the function which is called when the time/tick count expire. Note that if the
|
||||
// in asynch mode, the callback could occur in a different thread from the thread that
|
||||
// started the timer.
|
||||
bool Callback::SetFunctionInfo(Callback::CallbackFunctionType pCallbackFunction, void* pCallbackArgument, bool bEnableRefCount)
|
||||
{
|
||||
if(pCallbackFunction)
|
||||
{
|
||||
mpFunction = pCallbackFunction;
|
||||
mpFunctionArg = pCallbackArgument;
|
||||
}
|
||||
else
|
||||
{
|
||||
mpFunction = DefaultCallback;
|
||||
mpFunctionArg = this;
|
||||
}
|
||||
|
||||
if(bEnableRefCount)
|
||||
{
|
||||
mbEnableRefCount = true;
|
||||
AddRefCallback(); // This will AddRef the pointer if it is non-NULL.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Callback::GetFunctionInfo(Callback::CallbackFunctionType& pCallbackFunction, void*& pCallbackArgument) const
|
||||
{
|
||||
pCallbackFunction = mpFunction;
|
||||
pCallbackArgument = mpFunctionArg;
|
||||
}
|
||||
|
||||
|
||||
void Callback::Call(uint64_t absoluteValue, uint64_t deltaValue)
|
||||
{
|
||||
EA_ASSERT(mpFunction);
|
||||
if(mpFunction)
|
||||
mpFunction(this, mpFunctionArg, absoluteValue, deltaValue);
|
||||
}
|
||||
|
||||
|
||||
uint64_t Callback::GetPeriod() const
|
||||
{
|
||||
return mPeriod;
|
||||
}
|
||||
|
||||
|
||||
bool Callback::SetPeriod(uint64_t nPeriod)
|
||||
{
|
||||
EA_ASSERT((int64_t)nPeriod > 0); // Sanity checks.
|
||||
mPeriod = nPeriod;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint64_t Callback::GetPrecision() const
|
||||
{
|
||||
return mPrecision;
|
||||
}
|
||||
|
||||
|
||||
bool Callback::SetPrecision(uint64_t nPrecision)
|
||||
{
|
||||
EA_ASSERT((int64_t)nPrecision >= 0);
|
||||
mPrecision = nPrecision;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Callback::Start(ICallbackManager* pCallbackManager, bool bOneShot)
|
||||
{
|
||||
if(!(int32_t)mbStarted) // Cast to int32_t because mbStarted might be an atomic int class.
|
||||
{
|
||||
if(!pCallbackManager)
|
||||
pCallbackManager = GetCallbackManager();
|
||||
|
||||
mpCallbackManager = pCallbackManager;
|
||||
|
||||
if(pCallbackManager)
|
||||
mbStarted = (mpCallbackManager->Add(this, bOneShot) ? 1 : 0);
|
||||
}
|
||||
|
||||
return ((int32_t)mbStarted != 0);
|
||||
}
|
||||
|
||||
|
||||
void Callback::Stop()
|
||||
{
|
||||
if((int32_t)mbStarted) // Cast to int32_t because mbStarted might be an atomic int class.
|
||||
{
|
||||
mpCallbackManager->Remove(this);
|
||||
mbStarted = 0;
|
||||
|
||||
// Note that the following may result in the Callback object (this)
|
||||
// being deleted, due to a reference count decrement on itself.
|
||||
// Thus it is important that this be the last thing done in this function.
|
||||
if(mbEnableRefCount)
|
||||
ReleaseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Callback::IsStarted() const
|
||||
{
|
||||
return ((int32_t)mbStarted != 0); // Cast to int32_t because mbStarted might be an atomic int class.
|
||||
}
|
||||
|
||||
|
||||
bool Callback::SetType(Type type)
|
||||
{
|
||||
mType = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Callback::Type Callback::GetType() const
|
||||
{
|
||||
return mType;
|
||||
}
|
||||
|
||||
|
||||
void Callback::AddRefCallback()
|
||||
{
|
||||
Call(kMessageAddRef, 0);
|
||||
}
|
||||
|
||||
|
||||
void Callback::ReleaseCallback()
|
||||
{
|
||||
Call(kMessageRelease, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CallbackVector
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CallbackManager::CallbackVector::CallbackVector()
|
||||
: mpBegin(mLocalBuffer),
|
||||
mpEnd(mLocalBuffer),
|
||||
mpCapacity(mLocalBuffer + EAArrayCount(mLocalBuffer))
|
||||
{
|
||||
#if defined(EA_DEBUG)
|
||||
memset(mLocalBuffer, 0, sizeof(mLocalBuffer));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
CallbackManager::CallbackVector::~CallbackVector()
|
||||
{
|
||||
if(mpBegin != mLocalBuffer)
|
||||
EASTDC_DELETE[] mpBegin; // It's OK if this is NULL; C++ allows it.
|
||||
}
|
||||
|
||||
|
||||
CallbackManager::CallbackVector::iterator CallbackManager::CallbackVector::erase(value_type* pIterator)
|
||||
{
|
||||
EA_ASSERT((pIterator >= mpBegin) && (pIterator < mpEnd));
|
||||
const size_t moveCount = (size_t)((mpEnd - pIterator) - 1);
|
||||
memmove(pIterator, pIterator + 1, moveCount * sizeof(value_type));
|
||||
|
||||
--mpEnd;
|
||||
|
||||
#if defined(EA_DEBUG)
|
||||
memset(mpEnd, 0, sizeof(value_type));
|
||||
#endif
|
||||
|
||||
return pIterator;
|
||||
}
|
||||
|
||||
|
||||
CallbackManager::CallbackVector::iterator CallbackManager::CallbackVector::push_back(value_type value)
|
||||
{
|
||||
if((mpEnd + 1) >= mpCapacity) // If there is insufficient existing capacity...
|
||||
{
|
||||
const size_t oldSize = (size_t)(mpEnd - mpBegin);
|
||||
const size_t oldCapacity = (size_t)(mpCapacity - mpBegin);
|
||||
const size_t newCapacity = (oldCapacity >= 2) ? (oldCapacity * 2) : 4;
|
||||
|
||||
value_type* pBegin = EASTDC_NEW("EACallback") value_type[newCapacity];
|
||||
EA_ASSERT(pBegin);
|
||||
memcpy(pBegin, mpBegin, oldSize * sizeof(value_type));
|
||||
if(mpBegin != mLocalBuffer)
|
||||
EASTDC_DELETE[] mpBegin;
|
||||
mpBegin = pBegin;
|
||||
mpEnd = pBegin + oldSize;
|
||||
mpCapacity = pBegin + newCapacity;
|
||||
}
|
||||
|
||||
*mpEnd = value;
|
||||
|
||||
return ++mpEnd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CallbackManager
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static ICallbackManager* gpCallbackManager = NULL;
|
||||
|
||||
EASTDC_API ICallbackManager* GetCallbackManager()
|
||||
{
|
||||
return gpCallbackManager;
|
||||
}
|
||||
|
||||
EASTDC_API void SetCallbackManager(ICallbackManager* pCallbackManager)
|
||||
{
|
||||
gpCallbackManager = pCallbackManager;
|
||||
}
|
||||
|
||||
|
||||
|
||||
CallbackManager::CallbackManager()
|
||||
: mCallbackArray()
|
||||
, mStopwatch(EA::StdC::Stopwatch::kUnitsNanoseconds)
|
||||
, mTickCounter(0)
|
||||
, mUserEventCounter(0)
|
||||
, mbInitialized(false)
|
||||
, mbRunning(false)
|
||||
, mbAsync(false)
|
||||
, mRandom()
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
, mNSecPerTick(10000000)
|
||||
, mNSecPerTickLastTimeMeasured(INT64_MIN)
|
||||
, mNSecPerTickLastTickMeasured(INT64_MIN)
|
||||
, mNextCallbackEventTime(0)
|
||||
, mNextCallbackEventTick(0)
|
||||
, mMutex()
|
||||
, mThread()
|
||||
, mbThreadStarted(0)
|
||||
, mThreadParam()
|
||||
#endif
|
||||
{
|
||||
// mCallbackArray.reserve(8); Disabled because it already has a built-in mLocalBuffer.
|
||||
}
|
||||
|
||||
CallbackManager::~CallbackManager()
|
||||
{
|
||||
CallbackManager::Shutdown();
|
||||
}
|
||||
|
||||
|
||||
bool CallbackManager::Init(bool bAsync, bool bAsyncStart
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
, EA::Thread::ThreadParameters threadParam
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if(!mbRunning)
|
||||
{
|
||||
mbAsync = bAsync;
|
||||
mbRunning = true;
|
||||
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
mThreadParam = threadParam;
|
||||
#else
|
||||
EA_ASSERT(!mbAsync);
|
||||
mbAsync = false; // The best we can do. Should never happen though.
|
||||
#endif
|
||||
|
||||
mStopwatch.Restart();
|
||||
|
||||
if(mbAsync && bAsyncStart)
|
||||
mbRunning = StartThread(); // If StartThread fails then set mbRunning to false.
|
||||
}
|
||||
|
||||
return mbRunning;
|
||||
}
|
||||
|
||||
|
||||
void CallbackManager::Shutdown()
|
||||
{
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_LOCK();
|
||||
|
||||
if(mbRunning)
|
||||
{
|
||||
mbRunning = false; // Set this to false so no further calls to CallbackManager will proceed.
|
||||
|
||||
StopThread();
|
||||
|
||||
mStopwatch.Stop();
|
||||
|
||||
// Stop all running Callbacks. This allows them to do cleanup.
|
||||
for(size_t i = 0, iEnd = mCallbackArray.size(); i < iEnd; ++i)
|
||||
{
|
||||
if(mCallbackArray[i]) // It's possible this could be NULL, because stopped callbacks are merely NULL their in the mCallbackArray.
|
||||
{
|
||||
Callback* pCallback = mCallbackArray[i]; // Make a temp because we will be unlocking our mutex below.
|
||||
mCallbackArray[i] = NULL; // Leave it as NULL for now. We'll actually erase the entry later during our update cycle. Our code is fine with NULL pointers and it's useful to keep them because their slots can be re-used.
|
||||
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_UNLOCK();
|
||||
pCallback->Stop();
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_LOCK();
|
||||
}
|
||||
}
|
||||
|
||||
mCallbackArray.clear();
|
||||
}
|
||||
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_UNLOCK();
|
||||
}
|
||||
|
||||
|
||||
// Returns true if the thread is running upon return of this function.
|
||||
// Will return true if the thread was already running upon calling this function.
|
||||
bool CallbackManager::StartThread()
|
||||
{
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
if(mbAsync)
|
||||
{
|
||||
if(mbThreadStarted.SetValueConditional(1, 0)) // If the thread was previously 0 and we set it to 1...
|
||||
{
|
||||
mThreadParam.mpName = "CallbackManager"; // Some platforms have an extremely limited thread name buffer and will clip this.
|
||||
EA::Thread::ThreadId threadId = mThread.Begin(RunStatic, static_cast<CallbackManager*>(this), &mThreadParam);
|
||||
|
||||
EA_ASSERT(threadId != EA::Thread::kThreadIdInvalid);
|
||||
return (threadId != EA::Thread::kThreadIdInvalid);
|
||||
}
|
||||
return true; // Else the thread was already running...
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CallbackManager::StopThread()
|
||||
{
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
if(mbThreadStarted.SetValueConditional(0, 1)) // If the thread was previously 1 and we set it to 0...
|
||||
{
|
||||
mThread.Wake(); // Should be a semaphore or condition variable signal.
|
||||
mThread.WaitForEnd();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CallbackManager::Update()
|
||||
{
|
||||
int64_t curTick = 0;
|
||||
int64_t curTime = 0;
|
||||
int64_t curUserEvent = 0;
|
||||
|
||||
UpdateInternal(curTick, curTime, curUserEvent);
|
||||
|
||||
EA_UNUSED(curTick);
|
||||
EA_UNUSED(curTime);
|
||||
EA_UNUSED(curUserEvent);
|
||||
}
|
||||
|
||||
|
||||
struct TempUnitsInfo
|
||||
{
|
||||
int64_t mUnits;
|
||||
int64_t* mpNextEventUnits;
|
||||
};
|
||||
|
||||
void CallbackManager::UpdateInternal(int64_t& curTick, int64_t& curTime, int64_t& curUserEvent)
|
||||
{
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_LOCK();
|
||||
|
||||
EA_ASSERT(mbRunning); // The user must have called CallbackManager::Init before using it.
|
||||
|
||||
curTick = ++mTickCounter;
|
||||
curTime = (int64_t)mStopwatch.GetElapsedTime();
|
||||
curUserEvent = (int64_t)mUserEventCounter;
|
||||
|
||||
if(!mCallbackArray.empty())
|
||||
{
|
||||
// Every time Update is called, we need to call the elapsed Callbacks and then
|
||||
// figure out the next time we'll need to do a callback.
|
||||
|
||||
// Scan our list and call the callbacks as needed
|
||||
int64_t nextCallBackUserEvent = 0;
|
||||
|
||||
TempUnitsInfo timeInfo = { curTime, &mNextCallbackEventTime };
|
||||
TempUnitsInfo tickInfo = { curTick, &mNextCallbackEventTick };
|
||||
TempUnitsInfo userEventInfo = { curUserEvent, &nextCallBackUserEvent };
|
||||
|
||||
for(size_t i = 0; i < mCallbackArray.size(); ++i) // Intentionally re-evaluate size every time through, as it could change dynamically below. Intentionally use < instead of !=, as size could decrease by any amount during the execution below.
|
||||
{
|
||||
Callback* pCallback = mCallbackArray[i];
|
||||
TempUnitsInfo* pTUI = NULL;
|
||||
|
||||
if(pCallback)
|
||||
{
|
||||
// Call the callback function if needed
|
||||
switch(pCallback->GetType())
|
||||
{
|
||||
case Callback::kTypeTime:
|
||||
pTUI = &timeInfo;
|
||||
break;
|
||||
|
||||
case Callback::kTypeTick:
|
||||
pTUI = &tickInfo;
|
||||
break;
|
||||
|
||||
default:
|
||||
case Callback::kTypeUserEvent:
|
||||
pTUI = &userEventInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
EA_ASSERT(pTUI != NULL);
|
||||
if(pTUI->mUnits >= pCallback->mNextCallbackEvent) // If it's time to call this callback...
|
||||
{
|
||||
// We have to beware that this Call might result in the callee manipulating
|
||||
// CallbackManager (us) and change our state, particularly with respect to
|
||||
// starting and stopping callbacks (including this callback).
|
||||
// As of this writing, our mutex is locked during the Call. This leaves an
|
||||
// opportunity for threading deadlock. To consider: See if we can unlock the
|
||||
// mutex before calling this. We would need to re-evaluate some of our state
|
||||
// upon return if we did this. Maybe have a member variable called mHasChanged
|
||||
// to make this more efficient.
|
||||
pCallback->Call((uint64_t)pTUI->mUnits, (uint64_t)(pTUI->mUnits - pCallback->mLastCallbackEvent));
|
||||
|
||||
if((i < mCallbackArray.size()) && (mCallbackArray[i] == pCallback)) // If the callback wasn't stopped and removed during the Call to the user above...
|
||||
{
|
||||
pCallback->mLastCallbackEvent = pTUI->mUnits;
|
||||
|
||||
if(pCallback->mbOneShot)
|
||||
pCallback->Stop();
|
||||
else
|
||||
{
|
||||
const int32_t precision = (int32_t)pCallback->GetPrecision();
|
||||
const int64_t period = (int64_t)pCallback->GetPeriod();
|
||||
|
||||
pCallback->mNextCallbackEvent = (pTUI->mUnits + period);
|
||||
|
||||
if(precision) // To consider; For kTypeTime it might be worth testing for (precision > 100) or similar instead here.
|
||||
{
|
||||
// An alternative to the use of random below would be a load minimization
|
||||
// strategy with quite a bit more involved implementation.
|
||||
const int32_t delta = RandomInt32UniformRange(mRandom, -precision, precision - 1); // Note by Paul P: I added this -1 so unit tests could pass, but it doesn't seem right.
|
||||
const int64_t nextCallbackEvent = pCallback->mNextCallbackEvent + delta;
|
||||
|
||||
if(nextCallbackEvent > pTUI->mUnits) // Ignore precision adjustments that make it so the next event is prior to the current one.
|
||||
pCallback->mNextCallbackEvent = nextCallbackEvent;
|
||||
}
|
||||
|
||||
EA_ASSERT(pCallback->mNextCallbackEvent >= pTUI->mUnits); // Assert that the next event is not backwards in time.
|
||||
|
||||
if(mbAsync)
|
||||
{
|
||||
if(*pTUI->mpNextEventUnits > pCallback->mNextCallbackEvent) // Update mNextCallbackEventTime or mNextCallbackEventTick to reflect what is the
|
||||
*pTUI->mpNextEventUnits = pCallback->mNextCallbackEvent; // minimum time until the next event. We'll use that in the thread Run function to
|
||||
} // know how long to sleep/wait before it needs to do another callback.
|
||||
}
|
||||
}
|
||||
}
|
||||
} // if(pCallback)
|
||||
else
|
||||
{
|
||||
mCallbackArray.erase(&mCallbackArray[i]);
|
||||
}
|
||||
|
||||
} // for(...)
|
||||
|
||||
} // if(!mCallbackArray.empty())
|
||||
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_UNLOCK();
|
||||
}
|
||||
|
||||
|
||||
// Thread function.
|
||||
intptr_t CallbackManager::Run()
|
||||
{
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
EA_ASSERT(mbThreadStarted.GetValue() != 0);
|
||||
|
||||
while(mbRunning)
|
||||
{
|
||||
int64_t curTick;
|
||||
int64_t curTime;
|
||||
int64_t curUserEvent;
|
||||
|
||||
UpdateInternal(curTick, curTime, curUserEvent);
|
||||
|
||||
// Update msec/tick value if needed.
|
||||
// Note by Paul Pedriana: I don't like this nanosecond tick calculation logic. IMO it's too delicate.
|
||||
const int64_t kNSecPerTickFrequency = UINT64_C(50000000); // in nsec -- how often to update mNSecPerTick
|
||||
|
||||
if(curTime > (mNSecPerTickLastTimeMeasured + kNSecPerTickFrequency))
|
||||
{
|
||||
mNSecPerTick = ((double)curTime - (double)mNSecPerTickLastTimeMeasured) / ((double)curTick - (double)mNSecPerTickLastTickMeasured);
|
||||
mNSecPerTickLastTimeMeasured = curTime;
|
||||
mNSecPerTickLastTickMeasured = curTick;
|
||||
}
|
||||
|
||||
// Come up with sleeping time and put the thread to sleep
|
||||
// To do: We need to switch this to an alternative synchronization primitive, as sleeping isn't very great.
|
||||
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) // If using a Microsoft OS where Wake is supported and we can sleep for a long time.
|
||||
int64_t timeToNextEventMs = INT_MAX;
|
||||
#else
|
||||
int64_t timeToNextEventMs = 50;
|
||||
#endif
|
||||
|
||||
if(!mCallbackArray.empty()) // If there are any active callbacks...
|
||||
{
|
||||
if(mNextCallbackEventTime < curTime)
|
||||
mNextCallbackEventTime = curTime + 100000000; // 100 milliseconds worth of nanoseconds. The number is arbitrary. Probably should be a smaller value for faster machines.
|
||||
if(mNextCallbackEventTick < curTick)
|
||||
mNextCallbackEventTick = curTick + 1000; // Arbitrary.
|
||||
|
||||
//const int64_t absoluteTime = (int64_t)mStopwatch.GetElapsedTime(); // Nanoseconds
|
||||
const int64_t timeDelta = mNextCallbackEventTime - curTime; // Nanoseconds
|
||||
const int64_t tickDelta = (int64_t)((mNextCallbackEventTick - curTick) * mNSecPerTick); // Nanoseconds
|
||||
const int64_t minDelta = smin(timeDelta, tickDelta); // Nanoseconds
|
||||
|
||||
timeToNextEventMs = (minDelta / 1000000) / 2; // Convert minDelta to milliseconds (what ThreadSleep wants) and half it in order to oversample the callback time (is this necessary?)
|
||||
|
||||
if(timeToNextEventMs < 0)
|
||||
timeToNextEventMs = 0; // simply yield.
|
||||
}
|
||||
|
||||
// Question by Paul Pedriana upon examining this code: Why is this implemented using thread sleeping instead of a conventional
|
||||
// thread synchronization primitive such as a Semaphore? At the least this should be a semaphore wait with a timeout set
|
||||
// to be equal to timeToNextEventMs. Then instead of waking a thread with Thread::Wake, we can simply signal the semaphore. This is
|
||||
// expecially so because some platforms don't support waking threads from sleep.
|
||||
|
||||
if(timeToNextEventMs == 0)
|
||||
Thread::ThreadSleep(EA::Thread::kTimeoutYield);
|
||||
else
|
||||
Thread::ThreadSleep(EA::Thread::ThreadTime(timeToNextEventMs));
|
||||
|
||||
//#if defined(EA_DEBUG)
|
||||
// static int64_t lastSleepTimes[200];
|
||||
// static int lastSleepTimeIndex = 0;
|
||||
// if(lastSleepTimeIndex == 200)
|
||||
// lastSleepTimeIndex = 0;
|
||||
// lastSleepTimes[lastSleepTimeIndex++] = timeToNextEventMs;
|
||||
//#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool CallbackManager::Add(Callback* pCallback, bool bOneShot)
|
||||
{
|
||||
bool bReturnValue = false;
|
||||
|
||||
EA_ASSERT(pCallback != NULL);
|
||||
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_LOCK();
|
||||
|
||||
if(mbRunning)
|
||||
{
|
||||
size_t found = 0xffffffff;
|
||||
size_t found_empty = 0xffffffff;
|
||||
|
||||
// See if pCallback is already added and while doing so see if there is an existing empty slot if it's not already added.
|
||||
for(size_t i = 0, iEnd = mCallbackArray.size(); i < iEnd; ++i)
|
||||
{
|
||||
Callback* pCallbackTemp = mCallbackArray[i];
|
||||
|
||||
if(pCallbackTemp == pCallback)
|
||||
{
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
else if(!pCallbackTemp && (found_empty == 0xffffffff))
|
||||
found_empty = i;
|
||||
}
|
||||
|
||||
if(found == 0xffffffff) // If pCallback isn't already present...
|
||||
{
|
||||
if(found_empty == 0xffffffff) // If no empty slot was found...
|
||||
mCallbackArray.push_back(pCallback);
|
||||
else
|
||||
mCallbackArray[found_empty] = pCallback;
|
||||
|
||||
int64_t units = 0; // This is the current time, current tick, or current user event number.
|
||||
int64_t nextUnits = 0;
|
||||
int64_t* pNextEventUnits = &nextUnits;
|
||||
int32_t precision = (int32_t)pCallback->GetPrecision();
|
||||
int64_t period = (int64_t)pCallback->GetPeriod();
|
||||
|
||||
switch(pCallback->GetType())
|
||||
{
|
||||
case Callback::kTypeTime: // If the callback triggers after a set amount of time...
|
||||
units = (int64_t)mStopwatch.GetElapsedTime();
|
||||
pNextEventUnits = &mNextCallbackEventTime;
|
||||
break;
|
||||
|
||||
case Callback::kTypeTick: // If the callback triggers after a set amount of ticks...
|
||||
units = (int64_t)mTickCounter;
|
||||
pNextEventUnits = &mNextCallbackEventTick;
|
||||
break;
|
||||
|
||||
case Callback::kTypeUserEvent: // If the callback triggers after a manually user-generated event...
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pCallback->mbOneShot = bOneShot;
|
||||
pCallback->mNextCallbackEvent = units + period;
|
||||
pCallback->mLastCallbackEvent = units;
|
||||
|
||||
if(precision)
|
||||
{
|
||||
const int32_t delta = RandomInt32UniformRange(mRandom, -precision, precision - 1); // Note by Paul P: I added this -1 so unit tests could pass, but it doesn't seem right.
|
||||
const int64_t nextCallbackEvent = pCallback->mNextCallbackEvent + delta;
|
||||
|
||||
if(nextCallbackEvent > pCallback->mNextCallbackEvent) // Ignore precision adjustments that make it so the next event is prior to the current one.
|
||||
pCallback->mNextCallbackEvent = nextCallbackEvent;
|
||||
}
|
||||
|
||||
EA_ASSERT(pCallback->mNextCallbackEvent >= units); // Assert that the next event is not backwards in time.
|
||||
|
||||
if(mbAsync)
|
||||
{
|
||||
// Note by Paul P: Is the following really supposed to use a < comparison? I didn't originally write this.
|
||||
// It works but I'm not sure it's the best way to do this. It seems to me that the next
|
||||
// event units should by default be a very long time from now and newly added Callback
|
||||
// objects should reduce that time. I think that this code here works because while we set a
|
||||
// mNextCallbackEventTime/mNextCallbackEventTick to be further in the future, the RunInternal function
|
||||
// will loop over Callback objects and select the actual soonest one. If we switched the > here to a >
|
||||
// then we would need to make sure we initially set mNextCallbackEventTime/mNextCallbackEventTick to be
|
||||
// a high value instead of it's initial default of 0, because if it starts as zero it will get stuck
|
||||
// there permanently because it never gets updated (I tried this so I know it happens like so).
|
||||
if(*pNextEventUnits < pCallback->mNextCallbackEvent)
|
||||
*pNextEventUnits = pCallback->mNextCallbackEvent;
|
||||
}
|
||||
}
|
||||
|
||||
bReturnValue = true; // This might turn false below in case of an error.
|
||||
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
if(mbAsync) // If we run in async (background thread) mode...
|
||||
{
|
||||
if(mbThreadStarted.GetValue() == 0) // If the thread hasn't been started yet...
|
||||
bReturnValue = StartThread(); // Starts it if not already started. Is there something useful we could do with the return value of this?
|
||||
|
||||
if((mNextCallbackEventTime < (int64_t)mStopwatch.GetElapsedTime()) || // If we need to wake the thread now to do a callback...
|
||||
(mNextCallbackEventTick < (int64_t)mTickCounter))
|
||||
{
|
||||
// Note: Some platforms don't have the capability of waking a sleeping thread.
|
||||
// This code should be using a semaphore instead of thread sleep/wake.
|
||||
mThread.Wake();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_UNLOCK();
|
||||
|
||||
return bReturnValue;
|
||||
}
|
||||
|
||||
|
||||
bool CallbackManager::Remove(Callback* pCallback)
|
||||
{
|
||||
bool bRemoved = false;
|
||||
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_LOCK();
|
||||
|
||||
if(pCallback)
|
||||
{
|
||||
if(mbRunning)
|
||||
{
|
||||
for(size_t i = 0, iEnd = mCallbackArray.size(); i < iEnd; ++i)
|
||||
{
|
||||
if(mCallbackArray[i] == pCallback)
|
||||
{
|
||||
mCallbackArray[i] = NULL; // We might re-use this slot later, so we don't take the CPU cycles to free the array right now.
|
||||
bRemoved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EA_CALLBACK_PROCESSOR_MUTEX_UNLOCK();
|
||||
|
||||
// It's important to call this outside our mutex lock.
|
||||
if(bRemoved)
|
||||
pCallback->Stop();
|
||||
|
||||
return bRemoved;
|
||||
}
|
||||
|
||||
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
|
||||
EA::Thread::Thread& CallbackManager::GetThread()
|
||||
{
|
||||
return mThread;
|
||||
}
|
||||
|
||||
void CallbackManager::Lock()
|
||||
{
|
||||
mMutex.Lock();
|
||||
}
|
||||
|
||||
void CallbackManager::Unlock()
|
||||
{
|
||||
mMutex.Unlock();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
void CallbackManager::OnUserEvent()
|
||||
{
|
||||
// To consider: Call the Update function here if callbacks waiting on user events are due.
|
||||
// The problem with doing this is that it makes OnUserEvent have side effects
|
||||
// which may be beyond what the user wants or expects.
|
||||
|
||||
#if EASTDC_THREADING_SUPPORTED
|
||||
// Note: Some platforms don't have the capability to wake a sleeping thread.
|
||||
// This code should be using a semaphore instead of thread sleep/wake.
|
||||
if(mThread.GetStatus() == EA::Thread::Thread::kStatusRunning)
|
||||
mThread.Wake();
|
||||
#endif
|
||||
|
||||
++mUserEventCounter;
|
||||
}
|
||||
|
||||
|
||||
uint64_t CallbackManager::GetTime()
|
||||
{
|
||||
return mStopwatch.GetElapsedTime();
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace StdC
|
||||
|
||||
} // namespace EA
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,547 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// See the header file for documentation and example usage.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/EAFixedPoint.h>
|
||||
#include <EAAssert/eaassert.h>
|
||||
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedMul
|
||||
//
|
||||
// returns a*b
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
EASTDC_API __declspec(naked)
|
||||
int32_t __stdcall SFixed16::FixedMul(const int32_t /*a*/, const int32_t /*b*/)
|
||||
{
|
||||
__asm{ // VC++ compiler will choke if you put ";" comments with a templated inline assembly function.
|
||||
mov eax, [esp+4] // Move a into eax
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
imul ecx // Multiply by b, storing the result in edx:eax
|
||||
shrd eax, edx, 16 // Do shift and leave result in eax.
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
int32_t FP_PASCAL SFixed16::FixedMul(const int32_t a, const int32_t b)
|
||||
{
|
||||
const int64_t c = (int64_t)a * b;
|
||||
return (int32_t)(c >> 16);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedMul
|
||||
//
|
||||
// returns a*b
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
EASTDC_API __declspec(naked)
|
||||
uint32_t __stdcall UFixed16::FixedMul(const uint32_t /*a*/, const uint32_t /*b*/)
|
||||
{
|
||||
__asm{ // VC++ compiler will choke if you put ";" comments with a templated inline assembly function.
|
||||
mov eax, [esp+4] // Move a into eax
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
mul ecx // Multiply by b, storing the result in edx:eax
|
||||
shrd eax, edx, 16 // Do shift and leave result in eax.
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
uint32_t FP_PASCAL UFixed16::FixedMul(const uint32_t a, const uint32_t b)
|
||||
{
|
||||
const uint64_t c = (uint64_t)a * b;
|
||||
return (uint32_t)(c >> 16);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedDiv
|
||||
//
|
||||
// returns a/b
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
int32_t FP_PASCAL SFixed16::FixedDiv(const int32_t /*a*/, const int32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov eax, [esp+4] // Prepare the number for division
|
||||
rol eax, 16 // Put the fractional part of the number in the top half of eax, rotating the top part to the bottom part for later use
|
||||
movsx edx, ax // Put the integer part in edx
|
||||
xor ax, ax // Clear the integer part from eax
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
idiv ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
int32_t FP_PASCAL SFixed16::FixedDiv(const int32_t a, const int32_t b)
|
||||
{
|
||||
const int64_t c = ((uint64_t)a) << 16;
|
||||
return (int32_t)(c / b);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UFixed16::FixedDiv
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
uint32_t __stdcall UFixed16::FixedDiv(const uint32_t /*a*/, const uint32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov eax, [esp+4] // Prepare the number for division
|
||||
rol eax, 16 // Put the fractional part of the number in the top half of eax, rotating the top part to the bottom part for later use
|
||||
movsx edx, ax // Put the integer part in edx
|
||||
xor ax, ax // Clear the integer part from eax
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
div ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
uint32_t FP_PASCAL UFixed16::FixedDiv(const uint32_t a, const uint32_t b)
|
||||
{
|
||||
const uint64_t c = ((uint64_t)a) << 16;
|
||||
return (uint32_t)(c / b);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedDivSafe
|
||||
//
|
||||
// returns a/b. Checks for overflow and returns max value if so, instead of bombing.
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
int32_t __stdcall SFixed16::FixedDivSafe(const int32_t /*a*/, const int32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov ecx, [esp+8] // Move b into ecx.
|
||||
or ecx, ecx // Test to see if equal to zero.
|
||||
je NOT_OK // if not OK
|
||||
mov eax, [esp+4] // Put a into eax.
|
||||
xor edx, edx // Clear out edx, since we'll be shifting into it.
|
||||
shld edx,eax,16 // double fixed
|
||||
cmp edx, ecx // do a test for overflow.
|
||||
jge NOT_OK // If edx is greater than ecx, then we will overflow on the divide.
|
||||
shl eax,16 // double fixed
|
||||
idiv ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
ret 8
|
||||
NOT_OK:
|
||||
mov eax, 0x7FFFFFFF // put in max value
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
int32_t FP_PASCAL SFixed16::FixedDivSafe(const int32_t a, const int32_t b)
|
||||
{
|
||||
if(b)
|
||||
{
|
||||
const int64_t c = ((int64_t)a) << 16;
|
||||
return (int32_t)(c / b);
|
||||
}
|
||||
return INT32_MAX;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UFixed16::FixedDivSafe
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
uint32_t __stdcall UFixed16::FixedDivSafe(const uint32_t /*a*/, const uint32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov ecx, [esp+8] // Move b into ecx.
|
||||
or ecx, ecx // Test to see if equal to zero.
|
||||
je NOT_OK // if not OK
|
||||
mov eax, [esp+4] // Put a into eax.
|
||||
xor edx, edx // Clear out edx, since we'll be shifting into it.
|
||||
shld edx,eax,16 // double fixed
|
||||
cmp edx, ecx // do a test for overflow.
|
||||
jae NOT_OK // If edx is greater than ecx, then we will overflow on the divide.
|
||||
shl eax,16 // double fixed
|
||||
div ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
ret 8 // return
|
||||
NOT_OK:
|
||||
mov eax, 0xFFFFFFFF // put in max value
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL //Intel C++
|
||||
return 0; //Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
uint32_t FP_PASCAL UFixed16::FixedDivSafe(const uint32_t a, const uint32_t b)
|
||||
{
|
||||
if(b)
|
||||
{
|
||||
const uint64_t c = ((uint64_t)a) << 16;
|
||||
return (uint32_t)(c / b);
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedMulDiv
|
||||
//
|
||||
// returns a*b/c Faster than separate mul and div.
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
int32_t __stdcall SFixed16::FixedMulDiv(const int32_t /*a*/, const int32_t /*b*/, const int32_t /*c*/)
|
||||
{
|
||||
__asm{
|
||||
mov eax, [esp+4] // First set up the multiply. Put a into eax.
|
||||
mov ecx, [esp+8] // Put b into ecx.
|
||||
imul ecx // Multiply. The result is in edx:eax.
|
||||
mov ecx, [esp+12] // Put the denominator into ecx.
|
||||
idiv ecx // Divide, leaving the result in eax and remainder in edx.
|
||||
ret 12
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
int32_t FP_PASCAL SFixed16::FixedMulDiv(const int32_t /*a*/, const int32_t /*b*/, const int32_t /*c*/)
|
||||
{
|
||||
// EA_ASSERT(false); // This is not yet finished.
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UFixed16::FixedMulDiv
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
uint32_t __stdcall UFixed16::FixedMulDiv(const uint32_t /*a*/, const uint32_t /*b*/, const uint32_t /*c*/)
|
||||
{
|
||||
__asm{
|
||||
mov eax, [esp+4] // First set up the multiply. Put a into eax.
|
||||
mov ecx, [esp+8] // Put b into ecx.
|
||||
mul ecx // Multiply. The result is in edx:eax.
|
||||
mov ecx, [esp+12] // Put the denominator into ecx.
|
||||
div ecx // Divide, leaving the result in eax and remainder in edx.
|
||||
ret 12
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL //Intel C++
|
||||
return 0; //Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
uint32_t FP_PASCAL UFixed16::FixedMulDiv(const uint32_t /*a*/, const uint32_t /*b*/, const uint32_t /*c*/)
|
||||
{
|
||||
// EA_ASSERT(false); // This is not yet finished.
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedMulDivSafe
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
int32_t __stdcall SFixed16::FixedMulDivSafe(const int32_t /*a*/, const int32_t /*b*/, const int32_t /*c*/)
|
||||
{
|
||||
__asm{
|
||||
mov ecx, [esp+12] // Move b into ecx.
|
||||
or ecx, ecx // Test to see if equal to zero.
|
||||
je NOT_OK // if not OK
|
||||
mov eax, [esp+4] //
|
||||
mov ebx, [esp+8] //
|
||||
imul ebx //
|
||||
cmp edx, ecx // do a test for overflow.
|
||||
jge NOT_OK // If edx is greater than ecx, then we will overflow on the divide.
|
||||
idiv ecx // Result is in eax.
|
||||
ret 12
|
||||
NOT_OK:
|
||||
mov eax, 0x7FFFFFFF // put in max value
|
||||
ret 12
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL //Intel C++
|
||||
return 0; //Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
int32_t FP_PASCAL SFixed16::FixedMulDivSafe(const int32_t /*a*/, const int32_t /*b*/, const int32_t /*c*/)
|
||||
{
|
||||
// EA_ASSERT(false); // This is not yet finished.
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UFixed16::FixedMulDivSafe
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
uint32_t __stdcall UFixed16::FixedMulDivSafe(const uint32_t /*a*/, const uint32_t /*b*/, const uint32_t /*c*/)
|
||||
{
|
||||
__asm{
|
||||
mov ecx, [esp+12] // Move b into ecx.
|
||||
or ecx, ecx // Test to see if equal to zero.
|
||||
je NOT_OK // if not OK
|
||||
mov eax, [esp+4] //
|
||||
mov ebx, [esp+8] //
|
||||
mul ebx //
|
||||
cmp edx, ecx // do a test for overflow.
|
||||
jae NOT_OK // If edx is greater than ecx, then we will overflow on the divide.
|
||||
div ecx // Result is in eax.
|
||||
ret 12
|
||||
NOT_OK:
|
||||
mov eax, 0xFFFFFFFF // put in max value
|
||||
ret 12
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL //Intel C++
|
||||
return 0; //Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
uint32_t FP_PASCAL UFixed16::FixedMulDivSafe(const uint32_t /*a*/, const uint32_t /*b*/, const uint32_t /*c*/)
|
||||
{
|
||||
// EA_ASSERT(false); // This is not yet finished.
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedMod
|
||||
//
|
||||
// returns a%b
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
int32_t __stdcall SFixed16::FixedMod(const int32_t /*a*/, const int32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov eax, [esp+4] // Prepare the number for division
|
||||
rol eax, 16 // Put the fractional part of the number in the top half of eax, rotating the top part to the bottom part for later use
|
||||
movsx edx, ax // Put the integer part in edx
|
||||
xor ax, ax // Clear the integer part from eax
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
idiv ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
mov eax, edx // The remainder after idiv is in edx. So we move it to eax before the return.
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
int32_t FP_PASCAL SFixed16::FixedMod(const int32_t a, const int32_t b)
|
||||
{
|
||||
const uint64_t c = ((uint64_t)a) << 16;
|
||||
return (int32_t)(c % b);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UFixed16::FixedMod
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
uint32_t __stdcall UFixed16::FixedMod(const uint32_t /*a*/, const uint32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov eax, [esp+4] // Prepare the number for division
|
||||
rol eax, 16 // Put the fractional part of the number in the top half of eax, rotating the top part to the bottom part for later use
|
||||
movsx edx, ax // Put the integer part in edx
|
||||
xor ax, ax // Clear the integer part from eax
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
div ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
mov eax, edx // The remainder after div is in edx. So we move it to eax before the return.
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
uint32_t FP_PASCAL UFixed16::FixedMod(const uint32_t a, const uint32_t b)
|
||||
{
|
||||
const uint64_t c = ((uint64_t)a) << 16;
|
||||
return (uint32_t)(c % b);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SFixed16::FixedModSafe
|
||||
//
|
||||
// returns a%b
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
int32_t __stdcall SFixed16::FixedModSafe(const int32_t /*a*/, const int32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
or ecx, ecx // Test to see if equal to zero.
|
||||
je NOT_OK // if not OK
|
||||
mov eax, [esp+4] // Prepare the number for division
|
||||
rol eax, 16 // Put the fractional part of the number in the top half of eax, rotating the top part to the bottom part for later use
|
||||
movsx edx, ax // Put the integer part in edx
|
||||
xor ax, ax // Clear the integer part from eax
|
||||
cmp edx, ecx // do a test for overflow.
|
||||
jge NOT_OK // If edx is greater than ecx, then we will overflow on the divide.
|
||||
div ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
mov eax, edx // The remainder after idiv is in edx. So we move it to eax before the return.
|
||||
ret 8
|
||||
NOT_OK:
|
||||
mov eax, 0xFFFFFFFF // put in max value
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
int32_t FP_PASCAL SFixed16::FixedModSafe(const int32_t a, const int32_t b)
|
||||
{
|
||||
if(b)
|
||||
{
|
||||
const uint64_t c = ((uint64_t)a) << 16;
|
||||
return (int32_t)(c % b);
|
||||
}
|
||||
return INT32_MAX;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UFixed16::FixedModSafe
|
||||
//
|
||||
#ifdef FP_USE_INTEL_ASM
|
||||
__declspec(naked)
|
||||
uint32_t __stdcall UFixed16::FixedModSafe(const uint32_t /*a*/, const uint32_t /*b*/)
|
||||
{
|
||||
__asm{
|
||||
mov ecx, [esp+8] // Move b into ecx
|
||||
or ecx, ecx // Test to see if equal to zero.
|
||||
je NOT_OK // if not OK
|
||||
mov eax, [esp+4] // Prepare the number for division
|
||||
rol eax, 16 // Put the fractional part of the number in the top half of eax, rotating the top part to the bottom part for later use
|
||||
movsx edx, ax // Put the integer part in edx
|
||||
xor ax, ax // Clear the integer part from eax
|
||||
cmp edx, ecx // do a test for overflow.
|
||||
jae NOT_OK // If edx is greater than ecx, then we will overflow on the divide.
|
||||
div ecx // Divide and store. Divide the signed 64 bit value in edx:eax by the 32 bit value in ecx.
|
||||
mov eax, edx // The remainder after div is in edx. So we move it to eax before the return.
|
||||
ret 8
|
||||
NOT_OK:
|
||||
mov eax, 0xFFFFFFFF // put in max value
|
||||
ret 8
|
||||
}
|
||||
#ifdef EA_COMPILER_INTEL // Intel C++
|
||||
return 0; // Just to get the compiler to shut up.
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<> EASTDC_API
|
||||
uint32_t FP_PASCAL UFixed16::FixedModSafe(const uint32_t a, const uint32_t b)
|
||||
{
|
||||
if(b)
|
||||
{
|
||||
const uint64_t c = ((uint64_t)a) << 16;
|
||||
return (uint32_t)(c % b);
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
|
||||
|
||||
// For unity build friendliness, undef all local #defines.
|
||||
#undef FP_USE_INTEL_ASM
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,914 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// OS globals are process-wide globals and are shared between an EXE and
|
||||
// DLLs. The OS global system works at the operating system level and has
|
||||
// auto-discovery logic so that no pointers or init calls need to be made
|
||||
// between modules for them to link their OS global systems together.
|
||||
//
|
||||
// Note that the interface to OS globals is a bit convoluted because the
|
||||
// core system needs to be thread-safe, cross-module, and independent of
|
||||
// app-level allocators. For objects for which order of initialization is
|
||||
// clearer, EASingleton is probably a better choice.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/EAGlobal.h>
|
||||
#include <EAStdC/internal/Thread.h>
|
||||
#include <EAStdC/EASprintf.h>
|
||||
#include <stdlib.h>
|
||||
#include <new>
|
||||
#include <EAAssert/eaassert.h>
|
||||
|
||||
// Until we can test other implementations of Linux, we enable Linux only for regular desktop x86 Linux.
|
||||
#if (defined(EA_PLATFORM_LINUX) && (defined(EA_PROCESSOR_X86) || defined(EA_PROCESSOR_X86_64)) && !defined(EA_PLATFORM_ANDROID)) // What other unix-like platforms can do this?
|
||||
#include <semaphore.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define EASTDC_EAGLOBAL_UNIX 1
|
||||
#else
|
||||
#define EASTDC_EAGLOBAL_UNIX 0
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
#pragma warning(push, 0)
|
||||
#include <Windows.h>
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4355) // warning C4355: 'this' : used in base member initializer list
|
||||
|
||||
#ifndef EASTDC_INIT_SEG_DEFINED
|
||||
#define EASTDC_INIT_SEG_DEFINED
|
||||
// Set initialization order between init_seg(compiler) (.CRT$XCC) and
|
||||
// init_seg(lib) (.CRT$XCL). The MSVC linker sorts the .CRT sections
|
||||
// alphabetically so we simply need to pick a name that is between
|
||||
// XCC and XCL. This works on both Windows and XBox.
|
||||
#pragma warning(disable: 4075) // "initializers put in unrecognized initialization area"
|
||||
#pragma init_seg(".CRT$XCF")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if EASTDC_GLOBALPTR_SUPPORT_ENABLED
|
||||
|
||||
namespace
|
||||
{
|
||||
struct OSGlobalManager
|
||||
{
|
||||
typedef EA::StdC::intrusive_list<EA::StdC::OSGlobalNode> OSGlobalList;
|
||||
|
||||
OSGlobalList mOSGlobalList;
|
||||
uint32_t mRefCount; //< Atomic reference count so that the allocator persists as long as the last module that needs it.
|
||||
EA::StdC::Mutex mcsLock;
|
||||
|
||||
OSGlobalManager();
|
||||
OSGlobalManager(const OSGlobalManager&);
|
||||
|
||||
OSGlobalManager& operator=(const OSGlobalManager&);
|
||||
|
||||
void Lock() {
|
||||
mcsLock.Lock();
|
||||
}
|
||||
|
||||
void Unlock() {
|
||||
mcsLock.Unlock();
|
||||
}
|
||||
|
||||
EA::StdC::OSGlobalNode* Find(uint32_t id);
|
||||
void Add(EA::StdC::OSGlobalNode* p);
|
||||
void Remove(EA::StdC::OSGlobalNode* p);
|
||||
};
|
||||
|
||||
OSGlobalManager::OSGlobalManager() {
|
||||
EA::StdC::AtomicSet(&mRefCount, 0);
|
||||
}
|
||||
|
||||
EA::StdC::OSGlobalNode* OSGlobalManager::Find(uint32_t id) {
|
||||
OSGlobalList::iterator it(mOSGlobalList.begin()), itEnd(mOSGlobalList.end());
|
||||
|
||||
for(; it!=itEnd; ++it) {
|
||||
EA::StdC::OSGlobalNode& node = *it;
|
||||
|
||||
if (node.mOSGlobalID == id)
|
||||
return &node;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void OSGlobalManager::Add(EA::StdC::OSGlobalNode *p) {
|
||||
mOSGlobalList.push_front(*p);
|
||||
}
|
||||
|
||||
void OSGlobalManager::Remove(EA::StdC::OSGlobalNode *p) {
|
||||
OSGlobalList::iterator it = mOSGlobalList.locate(*p);
|
||||
mOSGlobalList.erase(it);
|
||||
}
|
||||
|
||||
OSGlobalManager* gpOSGlobalManager = NULL;
|
||||
uint32_t gOSGlobalRefs = 0;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
|
||||
namespace {
|
||||
|
||||
#define EA_GLOBAL_UNIQUE_NAME_FORMAT "SingleMgrMutex%08x"
|
||||
#define EA_GLOBAL_UNIQUE_NAME_FORMAT_W L"SingleMgrMutex%08x"
|
||||
|
||||
// For the Microsoft desktop API (e.g. Win32, Win64) we can use Get/SetEnvironmentVariable to
|
||||
// read/write a process-global variable. But other Microsoft APIs (e.g. XBox 360) don't support
|
||||
// this and we resort to using a semaphore to store pointer bits.
|
||||
#if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 4)
|
||||
HANDLE ghOSGlobalManagerPtrSemaphore;
|
||||
#elif !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 8)
|
||||
HANDLE ghOSGlobalManagerPtrSemaphoreHi = NULL;
|
||||
HANDLE ghOSGlobalManagerPtrSemaphoreLo = NULL;
|
||||
#endif
|
||||
|
||||
bool InitOSGlobalSystem();
|
||||
void ShutdownOSGlobalSystem();
|
||||
OSGlobalManager* CreateOSGlobalManager();
|
||||
void DestroyOSGlobalManager(OSGlobalManager* pOSGlobalManager);
|
||||
|
||||
|
||||
OSGlobalManager* CreateOSGlobalManager()
|
||||
{
|
||||
// Allocate the OSGlobal manager in the heap. We use the heap so that it can
|
||||
// hop between DLLs if the EXE itself doesn't use the manager. Note that this
|
||||
// must be the operating system heap and not an app-level heap (i.e. PPMalloc).
|
||||
// We store the pointer to the originally allocated memory at p[-1], because we
|
||||
// may have moved it during alignment.
|
||||
const size_t kAlignment = 16;
|
||||
|
||||
void* p = HeapAlloc(GetProcessHeap(), 0, sizeof(OSGlobalManager) + kAlignment - 1 + sizeof(void *));
|
||||
void* pAligned = (void *)(((uintptr_t)p + sizeof(void *) + kAlignment - 1) & ~(kAlignment-1));
|
||||
((void**)pAligned)[-1] = p;
|
||||
|
||||
// Placement-new the global manager into the new memory.
|
||||
return new(pAligned) OSGlobalManager;
|
||||
}
|
||||
|
||||
|
||||
void DestroyOSGlobalManager(OSGlobalManager* pOSGlobalManager)
|
||||
{
|
||||
if(pOSGlobalManager)
|
||||
{
|
||||
gpOSGlobalManager->~OSGlobalManager();
|
||||
HeapFree(GetProcessHeap(), 0, ((void**)gpOSGlobalManager)[-1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool InitOSGlobalSystem()
|
||||
{
|
||||
// The following check is not thread-safe. On most platforms this isn't an
|
||||
// issue in practice because this function is called on application startup
|
||||
// and other threads won't be active. The primary concern is if the
|
||||
// memory changes that result below are visible to other processors later.
|
||||
if (!gpOSGlobalManager)
|
||||
{
|
||||
// We create a named (process-global) mutex. Other threads or modules within
|
||||
// this process share this same underlying mutex.
|
||||
#if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
|
||||
// The kernel object namespace is global on Win32 so we have to choose a unique name.
|
||||
wchar_t uniqueName[64];
|
||||
|
||||
EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT_W, (unsigned)GetCurrentProcessId());
|
||||
HANDLE hMutex = CreateMutexExW(NULL, uniqueName, CREATE_MUTEX_INITIAL_OWNER, SYNCHRONIZE);
|
||||
#else
|
||||
// The kernel object namespace is global on Win32 so we have to choose a unique name.
|
||||
char uniqueName[64];
|
||||
|
||||
EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned)GetCurrentProcessId());
|
||||
HANDLE hMutex = CreateMutexA(NULL, FALSE, uniqueName);
|
||||
#endif
|
||||
|
||||
if (!hMutex)
|
||||
return false;
|
||||
|
||||
if (WAIT_FAILED != WaitForSingleObjectEx(hMutex, INFINITE, FALSE))
|
||||
{
|
||||
// If we don't have access to GetEnvironmentVariable and are a 32 bit platform...
|
||||
#if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 4)
|
||||
// Xenon does not have memory mapping so we can't use the same technique
|
||||
// as for Win32, and lacks GetModuleHandle() so the old "get global proc
|
||||
// from main executable" trick won't work. What we do here is create
|
||||
// a semaphore whose value is the pointer to gpOSGlobalManager. Semaphores
|
||||
// can't hold negative values so we shift the pointer down by 4 bits
|
||||
// before storing it (it has 16 byte alignment so this is OK). Also,
|
||||
// there is no way to read a semaphore without modifying it so a mutex
|
||||
// is needed around the operation.
|
||||
|
||||
wchar_t uniqueSemaphoreName[32];
|
||||
EA::StdC::Sprintf(uniqueSemaphoreName, L"SingleMgr%u", (unsigned)GetCurrentProcessId());
|
||||
ghOSGlobalManagerPtrSemaphore = CreateSemaphoreExW(NULL, 0, 0x7FFFFFFF, uniqueSemaphoreName, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
|
||||
|
||||
if (ghOSGlobalManagerPtrSemaphore)
|
||||
{
|
||||
const bool bSemaphoreExists = (GetLastError() == ERROR_ALREADY_EXISTS);
|
||||
|
||||
if (bSemaphoreExists) // If somebody within our process already created it..
|
||||
{
|
||||
LONG ptrValue;
|
||||
|
||||
// Read the semaphore value.
|
||||
if (ReleaseSemaphore(ghOSGlobalManagerPtrSemaphore, 1, &ptrValue)) {
|
||||
// Undo the +1 we added to the semaphore.
|
||||
WaitForSingleObjectEx(ghOSGlobalManagerPtrSemaphore, INFINITE, FALSE);
|
||||
|
||||
// Recover the allocator pointer from the semaphore's original value.
|
||||
gpOSGlobalManager = (OSGlobalManager *)((uintptr_t)ptrValue << 4);
|
||||
}
|
||||
else
|
||||
EA_FAIL();
|
||||
}
|
||||
else
|
||||
{
|
||||
gpOSGlobalManager = CreateOSGlobalManager();
|
||||
|
||||
// Set the semaphore to the pointer value. It was created with
|
||||
// zero as the initial value so we add the desired value here.
|
||||
ReleaseSemaphore(ghOSGlobalManagerPtrSemaphore, (LONG)((uintptr_t)gpOSGlobalManager >> 4), NULL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have a system failure we have no way of handling.
|
||||
EA_FAIL();
|
||||
}
|
||||
|
||||
// If we don't have access to GetEnvironmentVariable and are a 64 bit platform...
|
||||
#elif !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 8)
|
||||
|
||||
// Semaphore counts are limited to 31 bits (LONG_MAX), but we need to store a 64 bit pointer
|
||||
// in those bits. But 64 bit pointers are always 64 bit aligned, so we need only 61 bits
|
||||
// to store a 64 bit pointer. So we store the upper 31 bits in one semaphore and the lower
|
||||
// 30 bits in another semaphore. Take the resulting 61 bits and shift left by 3 to get the
|
||||
// full 64 bit pointer.
|
||||
// We use CreateSemaphoreExW instead of CreateSemaphoreW because the latter isn't always
|
||||
// present in the non-desktop API.
|
||||
|
||||
// The kernel object namespace is session-local (not the same as app-local) so we have to choose a unique name.
|
||||
wchar_t uniqueNameHi[64];
|
||||
wchar_t uniqueNameLo[64];
|
||||
DWORD dwProcessId = GetCurrentProcessId();
|
||||
EA::StdC::Sprintf(uniqueNameHi, L"SingleMgrHi%u", dwProcessId);
|
||||
EA::StdC::Sprintf(uniqueNameLo, L"SingleMgrLo%u", dwProcessId);
|
||||
|
||||
// Create the high semaphore with a max of 31 bits of storage (0x7FFFFFFF).
|
||||
ghOSGlobalManagerPtrSemaphoreHi = CreateSemaphoreExW(NULL, 0, 0x7FFFFFFF, uniqueNameHi, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
|
||||
|
||||
if(ghOSGlobalManagerPtrSemaphoreHi)
|
||||
{
|
||||
const bool bSemaphoreExists = (GetLastError() == ERROR_ALREADY_EXISTS);
|
||||
LONG ptrValueHi;
|
||||
LONG ptrValueLo;
|
||||
|
||||
if(bSemaphoreExists) // If somebody within our process already created it..
|
||||
{
|
||||
// Read the semaphore value.
|
||||
if(ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreHi, 1, &ptrValueHi))
|
||||
{
|
||||
WaitForSingleObjectEx(ghOSGlobalManagerPtrSemaphoreHi, INFINITE, FALSE); // Undo the +1 we added with ReleaseSemaphore.
|
||||
|
||||
// We still need to create our ghOSGlobalManagerPtrSemaphoreLo, which should also already exist,
|
||||
// since some other module in this process has already exected this function. Create it with a
|
||||
// max of 30 bits of storage (0x3FFFFFFF).
|
||||
EA_ASSERT(ghOSGlobalManagerPtrSemaphoreLo == NULL);
|
||||
ghOSGlobalManagerPtrSemaphoreLo = CreateSemaphoreExW(NULL, 0, 0x3FFFFFFF, uniqueNameLo, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
|
||||
EA_ASSERT(GetLastError() == ERROR_ALREADY_EXISTS);
|
||||
|
||||
ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreLo, 1, &ptrValueLo);
|
||||
WaitForSingleObjectEx(ghOSGlobalManagerPtrSemaphoreLo, INFINITE, FALSE); // Undo the +1 we added with ReleaseSemaphore.
|
||||
|
||||
// Recover the allocator pointer from the semaphore's original value.
|
||||
uintptr_t ptr = (((uintptr_t)ptrValueHi) << 30 | ptrValueLo) << 3; // Combine the pair into 61 bits, and shift left by 3. This is the reverse of the code below.
|
||||
gpOSGlobalManager = (OSGlobalManager*)ptr;
|
||||
}
|
||||
else
|
||||
EA_FAIL(); // In practice this cannot happen unless the machine is cripped beyond repair.
|
||||
}
|
||||
else // Else our CreateSemaphorExW call was the first one to create ghOSGlobalManagerPtrSemaphoreHi.
|
||||
{
|
||||
gpOSGlobalManager = CreateOSGlobalManager();
|
||||
EA_ASSERT(gpOSGlobalManager && (((uintptr_t)gpOSGlobalManager & 7) == 0)); // All pointers on 64 bit platforms should have their lower 3 bits unused.
|
||||
|
||||
// Set the semaphore to the pointer value. It was created with
|
||||
// zero as the initial value so we add the desired value here.
|
||||
uintptr_t ptr = (uintptr_t)gpOSGlobalManager >> 3; // ptr now has the 61 significant bits of gpOSGlobalManager.
|
||||
|
||||
ptrValueHi = static_cast<LONG>(ptr >> 30); // ptrValueHi has the upper 31 of the 61 bits.
|
||||
ptrValueLo = static_cast<LONG>(ptr & 0x3FFFFFFF); // ptrValueLo has the lower 30 of the 61 bits.
|
||||
|
||||
// We still need to create our ghOSGlobalManagerPtrSemaphoreLo, which should also not already exist.
|
||||
// Create it with a max of 30 bits of storage (0x3FFFFFFF).
|
||||
EA_ASSERT(ghOSGlobalManagerPtrSemaphoreLo == NULL);
|
||||
ghOSGlobalManagerPtrSemaphoreLo = CreateSemaphoreExW(NULL, 0, 0x3FFFFFFF, uniqueNameLo, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
|
||||
EA_ASSERT(GetLastError() != ERROR_ALREADY_EXISTS);
|
||||
|
||||
EA_ASSERT((ghOSGlobalManagerPtrSemaphoreHi != NULL) && (ghOSGlobalManagerPtrSemaphoreLo != NULL)); // Should always be true, due to the logic in this function.
|
||||
ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreHi, ptrValueHi, NULL);
|
||||
ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreLo, ptrValueLo, NULL);
|
||||
|
||||
// Now semaphoreHi has the upper 31 significant bits of the gpOSGlobalManager pointer value.
|
||||
// and semaphoreLo has the lower 30 significant bits of the gpOSGlobalManager pointer value.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have a system failure we have no way of handling.
|
||||
EA_FAIL();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Under Win32 and Win64, we use system environment variables to store the gpOSGlobalManager value.
|
||||
char stringPtr[32];
|
||||
const DWORD dwResult = GetEnvironmentVariableA(uniqueName, stringPtr, 32);
|
||||
|
||||
if((dwResult > 0) && dwResult < 32 && stringPtr[0]) // If the variable was found...
|
||||
gpOSGlobalManager = (OSGlobalManager *)(uintptr_t)_strtoui64(stringPtr, NULL, 16); // _strtoui64 is a VC++ extension function.
|
||||
else
|
||||
{
|
||||
// GetLastError() should be ERROR_ENVVAR_NOT_FOUND. But what do we do if it isn't?
|
||||
gpOSGlobalManager = CreateOSGlobalManager();
|
||||
|
||||
EA::StdC::Sprintf(stringPtr, "%I64x", (uint64_t)(uintptr_t)gpOSGlobalManager);
|
||||
SetEnvironmentVariableA(uniqueName, stringPtr); // There's not much we can do if this call fails.
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
EA_ASSERT(gpOSGlobalManager && (gpOSGlobalManager->mRefCount < UINT32_MAX));
|
||||
EA::StdC::AtomicIncrement(&gpOSGlobalManager->mRefCount);
|
||||
|
||||
BOOL result = ReleaseMutex(hMutex);
|
||||
EA_ASSERT(result); EA_UNUSED(result);
|
||||
}
|
||||
|
||||
BOOL result = CloseHandle(hMutex);
|
||||
EA_ASSERT(result); EA_UNUSED(result);
|
||||
|
||||
if (!gpOSGlobalManager)
|
||||
{
|
||||
ShutdownOSGlobalSystem();
|
||||
return false;
|
||||
}
|
||||
|
||||
EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
|
||||
EA::StdC::AtomicIncrement(&gOSGlobalRefs); // Increment it once for the init of this system (InitOSGlobalSystem). This increment will be matched by a decrement in ShutdownOSGlobalSystem.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShutdownOSGlobalSystem()
|
||||
{
|
||||
if (EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
|
||||
{
|
||||
if (gpOSGlobalManager)
|
||||
{
|
||||
if (EA::StdC::AtomicDecrement(&gpOSGlobalManager->mRefCount) == 0)
|
||||
DestroyOSGlobalManager(gpOSGlobalManager);
|
||||
|
||||
gpOSGlobalManager = NULL;
|
||||
}
|
||||
|
||||
#if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 4)
|
||||
if (ghOSGlobalManagerPtrSemaphore)
|
||||
{
|
||||
CloseHandle(ghOSGlobalManagerPtrSemaphore);
|
||||
ghOSGlobalManagerPtrSemaphore = NULL;
|
||||
}
|
||||
#elif !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 8)
|
||||
if (ghOSGlobalManagerPtrSemaphoreHi)
|
||||
{
|
||||
CloseHandle(ghOSGlobalManagerPtrSemaphoreHi);
|
||||
ghOSGlobalManagerPtrSemaphoreHi = NULL;
|
||||
}
|
||||
|
||||
if (ghOSGlobalManagerPtrSemaphoreLo)
|
||||
{
|
||||
CloseHandle(ghOSGlobalManagerPtrSemaphoreLo);
|
||||
ghOSGlobalManagerPtrSemaphoreLo = NULL;
|
||||
}
|
||||
#else
|
||||
// Clear the gpOSGlobalManager environment variable.
|
||||
// This code needs to be called in a thread-safe way by the user, usually by calling it once on shutdown.
|
||||
// We have a problem if this function is executing at the same time some other entity in this process
|
||||
// is currently doing some new use of the OS global system, as that can cause an instance of gpOSGlobalManager
|
||||
// to be created while we are in the process of destroying it.
|
||||
char uniqueName[64]; uniqueName[0] = 0;
|
||||
|
||||
EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned)GetCurrentProcessId());
|
||||
SetEnvironmentVariableA(uniqueName, NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#elif defined(EA_PLATFORM_SONY)
|
||||
|
||||
// On this platform we use sceKernelReserveVirtualRange with a specific predetermined address, and place our
|
||||
// OSGlobalManager there. There's a bit of careful code below to deal with possible snafus while doing this,
|
||||
// and even with that this code still has some caveats about its usage, as described below. Most of the time
|
||||
// those caveats won't come into play, as they are relevant only for very unusual application usage patterns
|
||||
// and can still be worked around if those patterns happen to come into play.
|
||||
|
||||
#include <sys/dmem.h>
|
||||
#include <kernel.h>
|
||||
#include <string.h>
|
||||
#include <sceerror.h>
|
||||
#include <eathread/eathread_sync.h>
|
||||
#include <EAStdC/EAStopwatch.h>
|
||||
|
||||
|
||||
struct OSGlobalManagerContainer
|
||||
{
|
||||
uint8_t mMagicNumber[16]; // This is a unique value which guarantees that this is the address of the OSGlobalManager.
|
||||
OSGlobalManager mOSGlobalManager;
|
||||
};
|
||||
|
||||
const size_t kMemSize = 16384;
|
||||
const uint64_t kAddr64 = SCE_KERNEL_APP_MAP_AREA_END_ADDR - kMemSize; // End of appication memory space. https://ps4.scedev.net/resources/documents/SDK/3.000/Kernel-Overview/0004.html
|
||||
const uint8_t kMagic[16] = { 0xD1, 0x4B, 0x78, 0x49, 0x81, 0xF1, 0x45, 0x0D, 0x91, 0x08, 0x1B, 0xA8, 0xE7, 0x8A, 0xD1, 0xD3 }; // Random bytes.
|
||||
const size_t kDirectMemoryLength = 16 * 1024; // 16 KB minimum for sceKernelAllocateDirectMemory
|
||||
bool gbKeepDirectMemory = false;
|
||||
off_t gDirectMemoryStartAddr = 0;
|
||||
|
||||
bool InitOSGlobalSystem()
|
||||
{
|
||||
// The code below involves a number of careful steps to implement sharing a memory address between DLLs.
|
||||
// We have this code because this platform provides no other means for sharing a global piece of memory
|
||||
// between modules. On other platforms (e.g. Unix) there are environment variables we can use, and on
|
||||
// others (e.g. Windows) there are uniquely named synchronization primitives we can use. On still others
|
||||
// there are writable semaphore disk files that can be used. On this platform there is no environment variable
|
||||
// support, synchronization primitives have names but are not unique like on Windows, and disk files do
|
||||
// exist but only if you tag your application manifest to support the /download0 file mount.
|
||||
|
||||
if(!gpOSGlobalManager)
|
||||
{
|
||||
uint64_t currentAddr = kAddr64;
|
||||
|
||||
int32_t err = sceKernelAllocateDirectMemory(
|
||||
0,
|
||||
SCE_KERNEL_MAIN_DMEM_SIZE,
|
||||
kDirectMemoryLength,
|
||||
0,
|
||||
#if defined(EA_PLATFORM_PS4)
|
||||
SCE_KERNEL_WB_ONION,
|
||||
#else
|
||||
SCE_KERNEL_MTYPE_C_SHARED,
|
||||
#endif
|
||||
&gDirectMemoryStartAddr);
|
||||
EA_ASSERT(err == SCE_OK);
|
||||
|
||||
if(err != SCE_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gbKeepDirectMemory = false;
|
||||
|
||||
while (!gpOSGlobalManager && (currentAddr >= (kAddr64 - EA::StdC::kKettleOSGlobalSearchSpace)))
|
||||
{
|
||||
void* addr = reinterpret_cast<void*>(currentAddr);
|
||||
OSGlobalManagerContainer* pOSGlobalManagerContainer = static_cast<OSGlobalManagerContainer*>(addr);
|
||||
int result = SCE_OK; // Disabled because it doesn't work how we need it to: = sceKernelReserveVirtualRange(&addr, kMemSize, SCE_KERNEL_MAP_FIXED | SCE_KERNEL_MAP_NO_OVERWRITE, 0);
|
||||
// Possible return values are SCE_OK, SCE_KERNEL_ERROR_EINVAL, and SCE_KERNEL_ERROR_ENOMEM.
|
||||
|
||||
if((result == SCE_KERNEL_ERROR_ENOMEM) || (result == SCE_OK))
|
||||
{
|
||||
// SCE_KERNEL_ERROR_ENOMEM occurs when the address has already been reserved, which may have been
|
||||
// done by a previous EAGlobal init occurred. With either that or SCE_OK, we call scekernelMapDirectMemory.
|
||||
// We call scekernelMapDirectMemory even if we get SCE_KERNEL_ERROR_ENOMEM because another thread may
|
||||
// have called sceKernelReserveVirtualRange first, but we may be executing simultaneously and execute
|
||||
// sceKernelMapDirectMemory first.
|
||||
|
||||
result = sceKernelMapNamedDirectMemory(&addr, kMemSize, SCE_KERNEL_PROT_CPU_READ | SCE_KERNEL_PROT_CPU_WRITE,
|
||||
SCE_KERNEL_MAP_FIXED | SCE_KERNEL_MAP_NO_OVERWRITE, gDirectMemoryStartAddr, 0, "EAOSGlobal");
|
||||
// Possible return values are SCE_OK, SCE_KERNEL_ERROR_EACCES, SCE_KERNEL_ERROR_EINVAL, and SCE_KERNEL_ERROR_ENOMEM.
|
||||
// Normally we expect that SCE_KERNEL_ERROR_ENOMEM or SCE_OK will be returned. If the sceKernelReserveVirtualRange
|
||||
// returned SCE_OK then usually sceKernalMapDirectMemory will return SCE_OK for us, because if we were the first
|
||||
// to call the reserve function then we will probably be the first to call the map function.
|
||||
|
||||
if((result == SCE_KERNEL_ERROR_ENOMEM) || (result == SCE_OK))
|
||||
{
|
||||
// At this point the memory at addr is mapped to our address space and we can attempt to read and write it.
|
||||
// To make sure this memory really is read/write for us, we query the kernel for its protection type.
|
||||
bool weWereHereFirst = (result == SCE_OK); // IF we were here first then we take care of initializing the pOSGlobalManagerContainer->mOSGlobalManager instance.
|
||||
|
||||
if(weWereHereFirst)
|
||||
{
|
||||
// We use some low level synchronization primitives here to accomplish memory synchronization. We are unable
|
||||
// to use a mutex to do this because there is no way to share a mutex anonymously between modules. The whole
|
||||
// reason EAGlobal exists is to allow sharing variables anonymously between modules. So we have a chicken and
|
||||
// egg problem: we can't share a mutex between modules until InitOSGlobalSystem has completed.
|
||||
|
||||
::new(&pOSGlobalManagerContainer->mOSGlobalManager) OSGlobalManager;
|
||||
EA::StdC::AtomicIncrement(&pOSGlobalManagerContainer->mOSGlobalManager.mRefCount);
|
||||
gpOSGlobalManager = &pOSGlobalManagerContainer->mOSGlobalManager;
|
||||
EAWriteBarrier(); // Make sure this is seen as written before the memcpy is seen as written.
|
||||
EACompilerMemoryBarrier(); // Don't let the compiler move the above code to after the below code.
|
||||
|
||||
memcpy(pOSGlobalManagerContainer->mMagicNumber, kMagic, sizeof(pOSGlobalManagerContainer->mMagicNumber));
|
||||
EAWriteBarrier(); // Make sure other threads see this write.
|
||||
|
||||
gbKeepDirectMemory = true;
|
||||
}
|
||||
else // Else somebody before us mapped this memory. We need to validate it before trying to use it.
|
||||
{
|
||||
// We have a problem in that as we execute this code, another thread might have just started executing the
|
||||
// the weWereHere = true pathway above. So the magic value might not have been written yet. We don't currently
|
||||
// have an easy means to deal with this other than to loop for N milliseconds while waiting for the other
|
||||
// thread to complete.
|
||||
// Another potential problem can occur if two threads of differing priorities execute on the same CPU and
|
||||
// one blocks the other from completing this code. That would be a
|
||||
|
||||
int protection = 0;
|
||||
result = sceKernelQueryMemoryProtection(addr, NULL, NULL, &protection);
|
||||
|
||||
if((result == SCE_OK) && (protection & SCE_KERNEL_PROT_CPU_READ) && (protection & SCE_KERNEL_PROT_CPU_WRITE))
|
||||
{
|
||||
EA::StdC::LimitStopwatch limitStopwatch(EA::StdC::Stopwatch::kUnitsMilliseconds, 500, true);
|
||||
|
||||
while(!limitStopwatch.IsTimeUp())
|
||||
{
|
||||
EAReadBarrier(); // Make sure we see the previous writes of other threads prior to the read below.
|
||||
|
||||
if(memcmp(kMagic, pOSGlobalManagerContainer->mMagicNumber, sizeof(pOSGlobalManagerContainer->mMagicNumber)) == 0) // If it's our memory...
|
||||
{
|
||||
// At this point we were able to create a new shared memory area or acquire the shared memory area that
|
||||
// some other InitOSGlobalSystem function call previously created.
|
||||
EA::StdC::AtomicIncrement(&pOSGlobalManagerContainer->mOSGlobalManager.mRefCount);
|
||||
gpOSGlobalManager = &pOSGlobalManagerContainer->mOSGlobalManager;
|
||||
EAWriteBarrier(); // This isn't strictly needed, but it can theoretically make things go faster for other threads.
|
||||
|
||||
break;
|
||||
}
|
||||
// Else either the other thread is still initializing pOSGlobalManagerContainer or some other completely
|
||||
// unrelated entity is using this memory for something else. We don't have a good means of detecting
|
||||
// the latter other than timing out. Maybe if sceKernelMapDirectMemory guarantees writing 0 to the memory
|
||||
// then we can exit this loop much faster.
|
||||
|
||||
// There must be another thread executing the weWereHereFirst = true pathway. We sleep so that thread
|
||||
// can complete that pathway. Possibly are are a higher priority thread which could be blocking it.
|
||||
SceKernelTimespec ts = { 0, 2000000 };
|
||||
sceKernelNanosleep(&ts, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try another address. The logic above has determined that the address was used by some other entity.
|
||||
// That may well indicate that we need to pick a new starting address to try, as we really want this to
|
||||
// always succeed the first time through. Note that this bumping up is safe and works as intended, because
|
||||
// if one module fails to work at the original address, all will (assuming that during startup some thread
|
||||
// doesn't map it before we try to get it and then later unmap it before the other modules that use this have loaded).
|
||||
currentAddr -= (1024 * 1024);
|
||||
|
||||
} // while ...
|
||||
|
||||
if(!gbKeepDirectMemory)
|
||||
{
|
||||
sceKernelReleaseDirectMemory(gDirectMemoryStartAddr, kDirectMemoryLength);
|
||||
gDirectMemoryStartAddr = 0;
|
||||
}
|
||||
} // if(!gpOSGlobalManager)
|
||||
|
||||
if(gpOSGlobalManager) // (We have an AddRef on gpOSGlobalManager from above, so gpOSGlobalManager can't possibly have become invalid at this point)
|
||||
{
|
||||
// gOSGlobalRefs measures the number of times InitOSGlobalSystem/ShutdowOSGlobalSystem was successfully called.
|
||||
EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
|
||||
EA::StdC::AtomicIncrement(&gOSGlobalRefs); // Increment it once for the init of this system (InitOSGlobalSystem). This increment will be matched by a decrement in ShutdownOSGlobalSystem.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void ShutdownOSGlobalSystem()
|
||||
{
|
||||
// gOSGlobalRefs measures the number of times InitOSGlobalSystem/ShutdowOSGlobalSystem was successfully called.
|
||||
if(EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
|
||||
{
|
||||
if(gpOSGlobalManager)
|
||||
{
|
||||
if(EA::StdC::AtomicDecrement(&gpOSGlobalManager->mRefCount) == 0) // mRefCount measures the use count of glOSGlobalManager.
|
||||
{
|
||||
// To consider: we can unmap the memory at gpOSGlobalManager - 16 bytes here. It doesn't buy us anything
|
||||
// aside from possibly making some tools see that we freed the mapped kernel memory.
|
||||
}
|
||||
if(gbKeepDirectMemory)
|
||||
{
|
||||
sceKernelReleaseDirectMemory(gDirectMemoryStartAddr, kDirectMemoryLength);
|
||||
gDirectMemoryStartAddr = 0;
|
||||
}
|
||||
gpOSGlobalManager = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif EASTDC_EAGLOBAL_UNIX
|
||||
|
||||
namespace {
|
||||
|
||||
#define EA_GLOBAL_UNIQUE_NAME_FORMAT "/SingleMgrMutex%llu"
|
||||
|
||||
OSGlobalManager* CreateOSGlobalManager();
|
||||
bool InitOSGlobalSystem();
|
||||
void ShutdownOSGlobalSystem();
|
||||
|
||||
|
||||
OSGlobalManager* CreateOSGlobalManager()
|
||||
{
|
||||
// Allocate the OSGlobal manager in shared memory.
|
||||
#if defined(__APPLE__)
|
||||
void* pMemory = mmap(NULL, sizeof(OSGlobalManager), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#else
|
||||
void* pMemory = mmap(NULL, sizeof(OSGlobalManager), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
#endif
|
||||
|
||||
if(pMemory) // Some Unix variants (e.g. mobile) can fail this call due to lack of support for it.
|
||||
{
|
||||
EA_ASSERT(((uintptr_t)pMemory & 15) == 0); // Make sure mmap returns at least 16 byte alignment.
|
||||
|
||||
// Placement-new the global manager into the new memory.
|
||||
return new(pMemory) OSGlobalManager;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void DestroyOSGlobalManager(OSGlobalManager* pOSGlobalManager)
|
||||
{
|
||||
if(pOSGlobalManager)
|
||||
{
|
||||
gpOSGlobalManager->~OSGlobalManager();
|
||||
munmap(pOSGlobalManager, sizeof(OSGlobalManager));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool InitOSGlobalSystem()
|
||||
{
|
||||
// The following check is not thread-safe. On most platforms this isn't an
|
||||
// issue in practice because this function is called on application startup
|
||||
// and other threads won't be active. The primary concern is if the
|
||||
// memory changes that result below are visible to other processors later.
|
||||
|
||||
if(!gpOSGlobalManager)
|
||||
{
|
||||
// We make a process-unique name based on the process id.
|
||||
char uniqueName[96];
|
||||
pid_t processID = getpid();
|
||||
|
||||
EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned long long)processID);
|
||||
sem_t* mutex = sem_open(uniqueName, O_CREAT, 0644, 1); // Unix has named semaphores but doesn't really have named mutexes, so we use a semaphore as a mutex.
|
||||
|
||||
if(mutex == SEM_FAILED)
|
||||
return false;
|
||||
|
||||
if(sem_wait(mutex) == 0) // If locking the mutex was successful...
|
||||
{
|
||||
// As of this writing, we are using getenv/setenv to write a shared variable pointer. It turns out that this
|
||||
// is not a good idea, because getenv/setenv is not thread-safe. getenv returns a pointer to static memory
|
||||
// which another thread (who isn't using our mutex) might call setenv in a way that changes that memory.
|
||||
// The opinion of the Linux people is that you just shouldn't ever call setenv during application runtime.
|
||||
// A better solution for us is to use shared mapped memory (shm_open(), mmap()): http://www.ibm.com/developerworks/aix/library/au-spunix_sharedmemory/index.html
|
||||
|
||||
const char* pName = getenv(uniqueName);
|
||||
|
||||
if(pName && pName[0]) // If the variable was found...
|
||||
gpOSGlobalManager = (OSGlobalManager*)(uintptr_t)strtoull(pName, NULL, 16);
|
||||
else
|
||||
{
|
||||
gpOSGlobalManager = CreateOSGlobalManager();
|
||||
|
||||
if(gpOSGlobalManager)
|
||||
{
|
||||
char buffer[32];
|
||||
EA::StdC::Sprintf(buffer, "%I64x", (uint64_t)(uintptr_t)gpOSGlobalManager);
|
||||
/*int result =*/ setenv(uniqueName, buffer, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if(gpOSGlobalManager)
|
||||
{
|
||||
EA_ASSERT(gpOSGlobalManager->mRefCount < UINT32_MAX);
|
||||
EA::StdC::AtomicIncrement(&gpOSGlobalManager->mRefCount);
|
||||
}
|
||||
|
||||
sem_post(mutex);
|
||||
sem_close(mutex);
|
||||
sem_unlink(uniqueName);
|
||||
}
|
||||
|
||||
if(!gpOSGlobalManager)
|
||||
{
|
||||
ShutdownOSGlobalSystem();
|
||||
return false;
|
||||
}
|
||||
|
||||
EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
|
||||
EA::StdC::AtomicIncrement(&gOSGlobalRefs); // Increment it once for the init of this system (InitOSGlobalSystem). This increment will be matched by a decrement in ShutdownOSGlobalSystem.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShutdownOSGlobalSystem()
|
||||
{
|
||||
if(EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
|
||||
{
|
||||
if(gpOSGlobalManager)
|
||||
{
|
||||
if(EA::StdC::AtomicDecrement(&gpOSGlobalManager->mRefCount) == 0)
|
||||
DestroyOSGlobalManager(gpOSGlobalManager);
|
||||
|
||||
gpOSGlobalManager = NULL;
|
||||
}
|
||||
|
||||
// Clear the gpOSGlobalManager environment variable.
|
||||
// This code needs to be called in a thread-safe way by the user, usually by calling it once on shutdown.
|
||||
// We have a problem if this function is executing at the same time some other entity in this process
|
||||
// is currently doing some new use of the OS global system, as that can cause an instance of gpOSGlobalManager
|
||||
// to be created while we are in the process of destroying it.
|
||||
char uniqueName[96]; uniqueName[0] = 0;
|
||||
pid_t processID = getpid();
|
||||
|
||||
EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned long long)processID);
|
||||
/*int result =*/ unsetenv(uniqueName);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#else // #if defined(EA_PLATFORM_MICROSOFT)
|
||||
|
||||
namespace {
|
||||
|
||||
static uint64_t sOSGlobalMgrMemory[(sizeof(OSGlobalManager) + 1) / sizeof(uint64_t)];
|
||||
|
||||
bool InitOSGlobalSystem()
|
||||
{
|
||||
// Theoretical problem: If you keep calling this function, eventually gOSGlobalRefs will overflow.
|
||||
EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
|
||||
if(EA::StdC::AtomicIncrement(&gOSGlobalRefs) == 1)
|
||||
gpOSGlobalManager = new(sOSGlobalMgrMemory) OSGlobalManager;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShutdownOSGlobalSystem()
|
||||
{
|
||||
if(EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
|
||||
gpOSGlobalManager = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // #if defined(EA_PLATFORM_MICROSOFT)
|
||||
|
||||
|
||||
|
||||
|
||||
EASTDC_API EA::StdC::OSGlobalNode* EA::StdC::GetOSGlobal(uint32_t id, OSGlobalFactoryPtr pFactory)
|
||||
{
|
||||
// Initialize up the OSGlobal system if we are getting called before
|
||||
// static init, i.e. allocator
|
||||
if (!InitOSGlobalSystem())
|
||||
return NULL;
|
||||
|
||||
gpOSGlobalManager->Lock();
|
||||
|
||||
EA::StdC::OSGlobalNode* p = gpOSGlobalManager->Find(id);
|
||||
|
||||
if (!p && pFactory)
|
||||
{
|
||||
p = pFactory();
|
||||
p->mOSGlobalID = id;
|
||||
AtomicSet(&p->mOSGlobalRefCount, 0);
|
||||
gpOSGlobalManager->Add(p);
|
||||
}
|
||||
|
||||
if (p)
|
||||
{
|
||||
EA_ASSERT(p->mOSGlobalRefCount < UINT32_MAX);
|
||||
AtomicIncrement(&p->mOSGlobalRefCount);
|
||||
|
||||
EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
|
||||
AtomicIncrement(&gOSGlobalRefs);
|
||||
}
|
||||
|
||||
gpOSGlobalManager->Unlock();
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API bool EA::StdC::SetOSGlobal(uint32_t id, EA::StdC::OSGlobalNode *p)
|
||||
{
|
||||
// Initialize up the OSGlobal system if we are getting called before
|
||||
// static init, i.e. allocator
|
||||
if (!InitOSGlobalSystem())
|
||||
return false;
|
||||
|
||||
gpOSGlobalManager->Lock();
|
||||
|
||||
EA::StdC::OSGlobalNode* const pTemp = gpOSGlobalManager->Find(id);
|
||||
|
||||
if (pTemp == NULL) // If there isn't one already...
|
||||
{
|
||||
p->mOSGlobalID = id;
|
||||
AtomicSet(&p->mOSGlobalRefCount, 0);
|
||||
gpOSGlobalManager->Add(p);
|
||||
|
||||
EA_ASSERT(p->mOSGlobalRefCount < UINT32_MAX);
|
||||
AtomicIncrement(&p->mOSGlobalRefCount);
|
||||
|
||||
EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
|
||||
AtomicIncrement(&gOSGlobalRefs);
|
||||
}
|
||||
|
||||
gpOSGlobalManager->Unlock();
|
||||
|
||||
return (pTemp == NULL);
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API bool EA::StdC::ReleaseOSGlobal(EA::StdC::OSGlobalNode *p)
|
||||
{
|
||||
gpOSGlobalManager->Lock();
|
||||
|
||||
const bool shouldDestroyManager = AtomicDecrement(&gOSGlobalRefs) == 0;
|
||||
const bool shouldDestroyOSGlobal = AtomicDecrement(&p->mOSGlobalRefCount) == 0;
|
||||
|
||||
if (shouldDestroyOSGlobal)
|
||||
gpOSGlobalManager->Remove(p);
|
||||
|
||||
gpOSGlobalManager->Unlock();
|
||||
|
||||
// Note by Paul Pedriana (10/2009): It seems to me that shouldDestroyManager will never
|
||||
// be true here because InitOSGlobalSystem will have been called at app startup and
|
||||
// its gOSGlobalRefs increment will still be live. So only when that last explicit
|
||||
// call to ShutdownOSGlobalSystem is called will gOSGlobalRefs go to zero.
|
||||
if (shouldDestroyManager)
|
||||
ShutdownOSGlobalSystem(); // This function decrements gOSGlobalRefs.
|
||||
|
||||
return shouldDestroyOSGlobal;
|
||||
}
|
||||
|
||||
|
||||
// Force the OSGlobal manager to be available for the life of the app.
|
||||
// It's OK if this comes up too late for some uses because GetOSGlobal()
|
||||
// will bring it online earlier in that case.
|
||||
namespace
|
||||
{
|
||||
struct AutoinitOSGlobalManager
|
||||
{
|
||||
AutoinitOSGlobalManager()
|
||||
{
|
||||
bool result = InitOSGlobalSystem();
|
||||
EA_ASSERT(result); EA_UNUSED(result);
|
||||
}
|
||||
|
||||
~AutoinitOSGlobalManager()
|
||||
{
|
||||
ShutdownOSGlobalSystem();
|
||||
}
|
||||
};
|
||||
|
||||
AutoinitOSGlobalManager gAutoinitOSGlobalManager;
|
||||
}
|
||||
|
||||
|
||||
#endif // EASTDC_GLOBALPTR_SUPPORT_ENABLED
|
||||
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
#pragma warning(pop) // symmetric for pop for the above --> #pragma warning(disable: 4355) // warning C4355: 'this' : used in base member initializer list
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/EAHashCRC.h>
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CRC16
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API uint16_t crc16Table[256] =
|
||||
{
|
||||
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
|
||||
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
|
||||
0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
|
||||
0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
|
||||
0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
|
||||
0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
|
||||
0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
|
||||
0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
|
||||
0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
|
||||
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
|
||||
0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
|
||||
0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
|
||||
0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
|
||||
0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
|
||||
0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
|
||||
0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
|
||||
0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
|
||||
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
|
||||
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
|
||||
0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
|
||||
0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
|
||||
0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
|
||||
0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
|
||||
0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
|
||||
0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
|
||||
0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
|
||||
0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
|
||||
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
|
||||
0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
|
||||
0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
|
||||
0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
|
||||
0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
|
||||
};
|
||||
|
||||
|
||||
EASTDC_API uint16_t CRC16(const void* pData, size_t nLength, uint16_t nInitialValue, bool bFinalize)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
const uint8_t* const pData8End = pData8 + nLength;
|
||||
unsigned tempValue = nInitialValue;
|
||||
|
||||
while(pData8 < pData8End)
|
||||
tempValue = ((tempValue >> 8) ^ crc16Table[(tempValue ^ *pData8++) & 0xff]);
|
||||
|
||||
if(bFinalize)
|
||||
tempValue = ~tempValue;
|
||||
|
||||
return (uint16_t)tempValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CRC24
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API uint32_t crc24Table[256] =
|
||||
{
|
||||
0x00000000, 0x00d6a776, 0x00f64557, 0x0020e221, 0x00b78115, 0x00612663, 0x0041c442, 0x00976334,
|
||||
0x00340991, 0x00e2aee7, 0x00c24cc6, 0x0014ebb0, 0x00838884, 0x00552ff2, 0x0075cdd3, 0x00a36aa5,
|
||||
0x00681322, 0x00beb454, 0x009e5675, 0x0048f103, 0x00df9237, 0x00093541, 0x0029d760, 0x00ff7016,
|
||||
0x005c1ab3, 0x008abdc5, 0x00aa5fe4, 0x007cf892, 0x00eb9ba6, 0x003d3cd0, 0x001ddef1, 0x00cb7987,
|
||||
0x00d02644, 0x00068132, 0x00266313, 0x00f0c465, 0x0067a751, 0x00b10027, 0x0091e206, 0x00474570,
|
||||
0x00e42fd5, 0x003288a3, 0x00126a82, 0x00c4cdf4, 0x0053aec0, 0x008509b6, 0x00a5eb97, 0x00734ce1,
|
||||
0x00b83566, 0x006e9210, 0x004e7031, 0x0098d747, 0x000fb473, 0x00d91305, 0x00f9f124, 0x002f5652,
|
||||
0x008c3cf7, 0x005a9b81, 0x007a79a0, 0x00acded6, 0x003bbde2, 0x00ed1a94, 0x00cdf8b5, 0x001b5fc3,
|
||||
0x00fb4733, 0x002de045, 0x000d0264, 0x00dba512, 0x004cc626, 0x009a6150, 0x00ba8371, 0x006c2407,
|
||||
0x00cf4ea2, 0x0019e9d4, 0x00390bf5, 0x00efac83, 0x0078cfb7, 0x00ae68c1, 0x008e8ae0, 0x00582d96,
|
||||
0x00935411, 0x0045f367, 0x00651146, 0x00b3b630, 0x0024d504, 0x00f27272, 0x00d29053, 0x00043725,
|
||||
0x00a75d80, 0x0071faf6, 0x005118d7, 0x0087bfa1, 0x0010dc95, 0x00c67be3, 0x00e699c2, 0x00303eb4,
|
||||
0x002b6177, 0x00fdc601, 0x00dd2420, 0x000b8356, 0x009ce062, 0x004a4714, 0x006aa535, 0x00bc0243,
|
||||
0x001f68e6, 0x00c9cf90, 0x00e92db1, 0x003f8ac7, 0x00a8e9f3, 0x007e4e85, 0x005eaca4, 0x00880bd2,
|
||||
0x00437255, 0x0095d523, 0x00b53702, 0x00639074, 0x00f4f340, 0x00225436, 0x0002b617, 0x00d41161,
|
||||
0x00777bc4, 0x00a1dcb2, 0x00813e93, 0x005799e5, 0x00c0fad1, 0x00165da7, 0x0036bf86, 0x00e018f0,
|
||||
0x00ad85dd, 0x007b22ab, 0x005bc08a, 0x008d67fc, 0x001a04c8, 0x00cca3be, 0x00ec419f, 0x003ae6e9,
|
||||
0x00998c4c, 0x004f2b3a, 0x006fc91b, 0x00b96e6d, 0x002e0d59, 0x00f8aa2f, 0x00d8480e, 0x000eef78,
|
||||
0x00c596ff, 0x00133189, 0x0033d3a8, 0x00e574de, 0x007217ea, 0x00a4b09c, 0x008452bd, 0x0052f5cb,
|
||||
0x00f19f6e, 0x00273818, 0x0007da39, 0x00d17d4f, 0x00461e7b, 0x0090b90d, 0x00b05b2c, 0x0066fc5a,
|
||||
0x007da399, 0x00ab04ef, 0x008be6ce, 0x005d41b8, 0x00ca228c, 0x001c85fa, 0x003c67db, 0x00eac0ad,
|
||||
0x0049aa08, 0x009f0d7e, 0x00bfef5f, 0x00694829, 0x00fe2b1d, 0x00288c6b, 0x00086e4a, 0x00dec93c,
|
||||
0x0015b0bb, 0x00c317cd, 0x00e3f5ec, 0x0035529a, 0x00a231ae, 0x007496d8, 0x005474f9, 0x0082d38f,
|
||||
0x0021b92a, 0x00f71e5c, 0x00d7fc7d, 0x00015b0b, 0x0096383f, 0x00409f49, 0x00607d68, 0x00b6da1e,
|
||||
0x0056c2ee, 0x00806598, 0x00a087b9, 0x007620cf, 0x00e143fb, 0x0037e48d, 0x001706ac, 0x00c1a1da,
|
||||
0x0062cb7f, 0x00b46c09, 0x00948e28, 0x0042295e, 0x00d54a6a, 0x0003ed1c, 0x00230f3d, 0x00f5a84b,
|
||||
0x003ed1cc, 0x00e876ba, 0x00c8949b, 0x001e33ed, 0x008950d9, 0x005ff7af, 0x007f158e, 0x00a9b2f8,
|
||||
0x000ad85d, 0x00dc7f2b, 0x00fc9d0a, 0x002a3a7c, 0x00bd5948, 0x006bfe3e, 0x004b1c1f, 0x009dbb69,
|
||||
0x0086e4aa, 0x005043dc, 0x0070a1fd, 0x00a6068b, 0x003165bf, 0x00e7c2c9, 0x00c720e8, 0x0011879e,
|
||||
0x00b2ed3b, 0x00644a4d, 0x0044a86c, 0x00920f1a, 0x00056c2e, 0x00d3cb58, 0x00f32979, 0x00258e0f,
|
||||
0x00eef788, 0x003850fe, 0x0018b2df, 0x00ce15a9, 0x0059769d, 0x008fd1eb, 0x00af33ca, 0x007994bc,
|
||||
0x00dafe19, 0x000c596f, 0x002cbb4e, 0x00fa1c38, 0x006d7f0c, 0x00bbd87a, 0x009b3a5b, 0x004d9d2d
|
||||
};
|
||||
|
||||
|
||||
EASTDC_API uint32_t CRC24(const void* pData, size_t nLength, uint32_t nInitialValue, bool bFinalize)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
const uint8_t* const pData8End = pData8 + nLength;
|
||||
|
||||
while(pData8 < pData8End)
|
||||
nInitialValue = (nInitialValue >> 8) ^ crc24Table[(nInitialValue ^ *pData8++) & 0xff];
|
||||
|
||||
if(bFinalize)
|
||||
{
|
||||
nInitialValue = ~nInitialValue;
|
||||
nInitialValue &= 0x00ffffff;
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CRC32
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API uint32_t crc32Table[256] =
|
||||
{
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
};
|
||||
|
||||
|
||||
EASTDC_API uint32_t CRC32(const void* pData, size_t nLength, uint32_t nInitialValue, bool bFinalize)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
const uint8_t* const pData8End = pData8 + nLength;
|
||||
|
||||
while(pData8 < pData8End)
|
||||
nInitialValue = (nInitialValue << 8) ^ crc32Table[(nInitialValue >> 24) ^ *pData8++];
|
||||
|
||||
if(bFinalize)
|
||||
nInitialValue = ~nInitialValue;
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
EASTDC_API uint32_t crc32TableReverse[256] =
|
||||
{
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
};
|
||||
|
||||
|
||||
EASTDC_API uint32_t CRC32Reverse(const void* pData, size_t nLength, uint32_t nInitialValue, bool bFinalize)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
|
||||
while(nLength >= 8)
|
||||
{
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
nLength -= 8;
|
||||
}
|
||||
|
||||
while(nLength--)
|
||||
nInitialValue = crc32TableReverse[(nInitialValue ^ *pData8++) & 0xff] ^ (nInitialValue >> 8);
|
||||
|
||||
if(bFinalize)
|
||||
nInitialValue = ~nInitialValue;
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
EASTDC_API uint32_t CRC32Rwstdc(const void* pData, size_t nLength)
|
||||
{
|
||||
unsigned result;
|
||||
const uint8_t* d = static_cast<const uint8_t*>(pData);
|
||||
|
||||
if(nLength < 4)
|
||||
{
|
||||
result = *d;
|
||||
|
||||
while(nLength--)
|
||||
result = ((result << 8) | *d++) ^ crc32Table[result & 0xff];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = static_cast<unsigned>(*d++ << 24);
|
||||
result |= *d++ << 16;
|
||||
result |= *d++ << 8;
|
||||
result |= *d++;
|
||||
result = ~result;
|
||||
|
||||
nLength -= 4;
|
||||
|
||||
for(size_t i = 0; i < nLength; i++)
|
||||
result = ((result << 8) | *d++) ^ crc32Table[result >> 24];
|
||||
|
||||
return (uint32_t)~result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CRC64
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// We follow the same polynomial convention as the ECMA-182 tape drive standard.
|
||||
EASTDC_API uint64_t crc64Table[256] =
|
||||
{
|
||||
UINT64_C(0x0000000000000000), UINT64_C(0x42f0e1eba9ea3693), UINT64_C(0x85e1c3d753d46d26), UINT64_C(0xc711223cfa3e5bb5),
|
||||
UINT64_C(0x493366450e42ecdf), UINT64_C(0x0bc387aea7a8da4c), UINT64_C(0xccd2a5925d9681f9), UINT64_C(0x8e224479f47cb76a),
|
||||
UINT64_C(0x9266cc8a1c85d9be), UINT64_C(0xd0962d61b56fef2d), UINT64_C(0x17870f5d4f51b498), UINT64_C(0x5577eeb6e6bb820b),
|
||||
UINT64_C(0xdb55aacf12c73561), UINT64_C(0x99a54b24bb2d03f2), UINT64_C(0x5eb4691841135847), UINT64_C(0x1c4488f3e8f96ed4),
|
||||
UINT64_C(0x663d78ff90e185ef), UINT64_C(0x24cd9914390bb37c), UINT64_C(0xe3dcbb28c335e8c9), UINT64_C(0xa12c5ac36adfde5a),
|
||||
UINT64_C(0x2f0e1eba9ea36930), UINT64_C(0x6dfeff5137495fa3), UINT64_C(0xaaefdd6dcd770416), UINT64_C(0xe81f3c86649d3285),
|
||||
UINT64_C(0xf45bb4758c645c51), UINT64_C(0xb6ab559e258e6ac2), UINT64_C(0x71ba77a2dfb03177), UINT64_C(0x334a9649765a07e4),
|
||||
UINT64_C(0xbd68d2308226b08e), UINT64_C(0xff9833db2bcc861d), UINT64_C(0x388911e7d1f2dda8), UINT64_C(0x7a79f00c7818eb3b),
|
||||
UINT64_C(0xcc7af1ff21c30bde), UINT64_C(0x8e8a101488293d4d), UINT64_C(0x499b3228721766f8), UINT64_C(0x0b6bd3c3dbfd506b),
|
||||
UINT64_C(0x854997ba2f81e701), UINT64_C(0xc7b97651866bd192), UINT64_C(0x00a8546d7c558a27), UINT64_C(0x4258b586d5bfbcb4),
|
||||
UINT64_C(0x5e1c3d753d46d260), UINT64_C(0x1cecdc9e94ace4f3), UINT64_C(0xdbfdfea26e92bf46), UINT64_C(0x990d1f49c77889d5),
|
||||
UINT64_C(0x172f5b3033043ebf), UINT64_C(0x55dfbadb9aee082c), UINT64_C(0x92ce98e760d05399), UINT64_C(0xd03e790cc93a650a),
|
||||
UINT64_C(0xaa478900b1228e31), UINT64_C(0xe8b768eb18c8b8a2), UINT64_C(0x2fa64ad7e2f6e317), UINT64_C(0x6d56ab3c4b1cd584),
|
||||
UINT64_C(0xe374ef45bf6062ee), UINT64_C(0xa1840eae168a547d), UINT64_C(0x66952c92ecb40fc8), UINT64_C(0x2465cd79455e395b),
|
||||
UINT64_C(0x3821458aada7578f), UINT64_C(0x7ad1a461044d611c), UINT64_C(0xbdc0865dfe733aa9), UINT64_C(0xff3067b657990c3a),
|
||||
UINT64_C(0x711223cfa3e5bb50), UINT64_C(0x33e2c2240a0f8dc3), UINT64_C(0xf4f3e018f031d676), UINT64_C(0xb60301f359dbe0e5),
|
||||
UINT64_C(0xda050215ea6c212f), UINT64_C(0x98f5e3fe438617bc), UINT64_C(0x5fe4c1c2b9b84c09), UINT64_C(0x1d14202910527a9a),
|
||||
UINT64_C(0x93366450e42ecdf0), UINT64_C(0xd1c685bb4dc4fb63), UINT64_C(0x16d7a787b7faa0d6), UINT64_C(0x5427466c1e109645),
|
||||
UINT64_C(0x4863ce9ff6e9f891), UINT64_C(0x0a932f745f03ce02), UINT64_C(0xcd820d48a53d95b7), UINT64_C(0x8f72eca30cd7a324),
|
||||
UINT64_C(0x0150a8daf8ab144e), UINT64_C(0x43a04931514122dd), UINT64_C(0x84b16b0dab7f7968), UINT64_C(0xc6418ae602954ffb),
|
||||
UINT64_C(0xbc387aea7a8da4c0), UINT64_C(0xfec89b01d3679253), UINT64_C(0x39d9b93d2959c9e6), UINT64_C(0x7b2958d680b3ff75),
|
||||
UINT64_C(0xf50b1caf74cf481f), UINT64_C(0xb7fbfd44dd257e8c), UINT64_C(0x70eadf78271b2539), UINT64_C(0x321a3e938ef113aa),
|
||||
UINT64_C(0x2e5eb66066087d7e), UINT64_C(0x6cae578bcfe24bed), UINT64_C(0xabbf75b735dc1058), UINT64_C(0xe94f945c9c3626cb),
|
||||
UINT64_C(0x676dd025684a91a1), UINT64_C(0x259d31cec1a0a732), UINT64_C(0xe28c13f23b9efc87), UINT64_C(0xa07cf2199274ca14),
|
||||
UINT64_C(0x167ff3eacbaf2af1), UINT64_C(0x548f120162451c62), UINT64_C(0x939e303d987b47d7), UINT64_C(0xd16ed1d631917144),
|
||||
UINT64_C(0x5f4c95afc5edc62e), UINT64_C(0x1dbc74446c07f0bd), UINT64_C(0xdaad56789639ab08), UINT64_C(0x985db7933fd39d9b),
|
||||
UINT64_C(0x84193f60d72af34f), UINT64_C(0xc6e9de8b7ec0c5dc), UINT64_C(0x01f8fcb784fe9e69), UINT64_C(0x43081d5c2d14a8fa),
|
||||
UINT64_C(0xcd2a5925d9681f90), UINT64_C(0x8fdab8ce70822903), UINT64_C(0x48cb9af28abc72b6), UINT64_C(0x0a3b7b1923564425),
|
||||
UINT64_C(0x70428b155b4eaf1e), UINT64_C(0x32b26afef2a4998d), UINT64_C(0xf5a348c2089ac238), UINT64_C(0xb753a929a170f4ab),
|
||||
UINT64_C(0x3971ed50550c43c1), UINT64_C(0x7b810cbbfce67552), UINT64_C(0xbc902e8706d82ee7), UINT64_C(0xfe60cf6caf321874),
|
||||
UINT64_C(0xe224479f47cb76a0), UINT64_C(0xa0d4a674ee214033), UINT64_C(0x67c58448141f1b86), UINT64_C(0x253565a3bdf52d15),
|
||||
UINT64_C(0xab1721da49899a7f), UINT64_C(0xe9e7c031e063acec), UINT64_C(0x2ef6e20d1a5df759), UINT64_C(0x6c0603e6b3b7c1ca),
|
||||
UINT64_C(0xf6fae5c07d3274cd), UINT64_C(0xb40a042bd4d8425e), UINT64_C(0x731b26172ee619eb), UINT64_C(0x31ebc7fc870c2f78),
|
||||
UINT64_C(0xbfc9838573709812), UINT64_C(0xfd39626eda9aae81), UINT64_C(0x3a28405220a4f534), UINT64_C(0x78d8a1b9894ec3a7),
|
||||
UINT64_C(0x649c294a61b7ad73), UINT64_C(0x266cc8a1c85d9be0), UINT64_C(0xe17dea9d3263c055), UINT64_C(0xa38d0b769b89f6c6),
|
||||
UINT64_C(0x2daf4f0f6ff541ac), UINT64_C(0x6f5faee4c61f773f), UINT64_C(0xa84e8cd83c212c8a), UINT64_C(0xeabe6d3395cb1a19),
|
||||
UINT64_C(0x90c79d3fedd3f122), UINT64_C(0xd2377cd44439c7b1), UINT64_C(0x15265ee8be079c04), UINT64_C(0x57d6bf0317edaa97),
|
||||
UINT64_C(0xd9f4fb7ae3911dfd), UINT64_C(0x9b041a914a7b2b6e), UINT64_C(0x5c1538adb04570db), UINT64_C(0x1ee5d94619af4648),
|
||||
UINT64_C(0x02a151b5f156289c), UINT64_C(0x4051b05e58bc1e0f), UINT64_C(0x87409262a28245ba), UINT64_C(0xc5b073890b687329),
|
||||
UINT64_C(0x4b9237f0ff14c443), UINT64_C(0x0962d61b56fef2d0), UINT64_C(0xce73f427acc0a965), UINT64_C(0x8c8315cc052a9ff6),
|
||||
UINT64_C(0x3a80143f5cf17f13), UINT64_C(0x7870f5d4f51b4980), UINT64_C(0xbf61d7e80f251235), UINT64_C(0xfd913603a6cf24a6),
|
||||
UINT64_C(0x73b3727a52b393cc), UINT64_C(0x31439391fb59a55f), UINT64_C(0xf652b1ad0167feea), UINT64_C(0xb4a25046a88dc879),
|
||||
UINT64_C(0xa8e6d8b54074a6ad), UINT64_C(0xea16395ee99e903e), UINT64_C(0x2d071b6213a0cb8b), UINT64_C(0x6ff7fa89ba4afd18),
|
||||
UINT64_C(0xe1d5bef04e364a72), UINT64_C(0xa3255f1be7dc7ce1), UINT64_C(0x64347d271de22754), UINT64_C(0x26c49cccb40811c7),
|
||||
UINT64_C(0x5cbd6cc0cc10fafc), UINT64_C(0x1e4d8d2b65facc6f), UINT64_C(0xd95caf179fc497da), UINT64_C(0x9bac4efc362ea149),
|
||||
UINT64_C(0x158e0a85c2521623), UINT64_C(0x577eeb6e6bb820b0), UINT64_C(0x906fc95291867b05), UINT64_C(0xd29f28b9386c4d96),
|
||||
UINT64_C(0xcedba04ad0952342), UINT64_C(0x8c2b41a1797f15d1), UINT64_C(0x4b3a639d83414e64), UINT64_C(0x09ca82762aab78f7),
|
||||
UINT64_C(0x87e8c60fded7cf9d), UINT64_C(0xc51827e4773df90e), UINT64_C(0x020905d88d03a2bb), UINT64_C(0x40f9e43324e99428),
|
||||
UINT64_C(0x2cffe7d5975e55e2), UINT64_C(0x6e0f063e3eb46371), UINT64_C(0xa91e2402c48a38c4), UINT64_C(0xebeec5e96d600e57),
|
||||
UINT64_C(0x65cc8190991cb93d), UINT64_C(0x273c607b30f68fae), UINT64_C(0xe02d4247cac8d41b), UINT64_C(0xa2dda3ac6322e288),
|
||||
UINT64_C(0xbe992b5f8bdb8c5c), UINT64_C(0xfc69cab42231bacf), UINT64_C(0x3b78e888d80fe17a), UINT64_C(0x7988096371e5d7e9),
|
||||
UINT64_C(0xf7aa4d1a85996083), UINT64_C(0xb55aacf12c735610), UINT64_C(0x724b8ecdd64d0da5), UINT64_C(0x30bb6f267fa73b36),
|
||||
UINT64_C(0x4ac29f2a07bfd00d), UINT64_C(0x08327ec1ae55e69e), UINT64_C(0xcf235cfd546bbd2b), UINT64_C(0x8dd3bd16fd818bb8),
|
||||
UINT64_C(0x03f1f96f09fd3cd2), UINT64_C(0x41011884a0170a41), UINT64_C(0x86103ab85a2951f4), UINT64_C(0xc4e0db53f3c36767),
|
||||
UINT64_C(0xd8a453a01b3a09b3), UINT64_C(0x9a54b24bb2d03f20), UINT64_C(0x5d45907748ee6495), UINT64_C(0x1fb5719ce1045206),
|
||||
UINT64_C(0x919735e51578e56c), UINT64_C(0xd367d40ebc92d3ff), UINT64_C(0x1476f63246ac884a), UINT64_C(0x568617d9ef46bed9),
|
||||
UINT64_C(0xe085162ab69d5e3c), UINT64_C(0xa275f7c11f7768af), UINT64_C(0x6564d5fde549331a), UINT64_C(0x279434164ca30589),
|
||||
UINT64_C(0xa9b6706fb8dfb2e3), UINT64_C(0xeb46918411358470), UINT64_C(0x2c57b3b8eb0bdfc5), UINT64_C(0x6ea7525342e1e956),
|
||||
UINT64_C(0x72e3daa0aa188782), UINT64_C(0x30133b4b03f2b111), UINT64_C(0xf7021977f9cceaa4), UINT64_C(0xb5f2f89c5026dc37),
|
||||
UINT64_C(0x3bd0bce5a45a6b5d), UINT64_C(0x79205d0e0db05dce), UINT64_C(0xbe317f32f78e067b), UINT64_C(0xfcc19ed95e6430e8),
|
||||
UINT64_C(0x86b86ed5267cdbd3), UINT64_C(0xc4488f3e8f96ed40), UINT64_C(0x0359ad0275a8b6f5), UINT64_C(0x41a94ce9dc428066),
|
||||
UINT64_C(0xcf8b0890283e370c), UINT64_C(0x8d7be97b81d4019f), UINT64_C(0x4a6acb477bea5a2a), UINT64_C(0x089a2aacd2006cb9),
|
||||
UINT64_C(0x14dea25f3af9026d), UINT64_C(0x562e43b4931334fe), UINT64_C(0x913f6188692d6f4b), UINT64_C(0xd3cf8063c0c759d8),
|
||||
UINT64_C(0x5dedc41a34bbeeb2), UINT64_C(0x1f1d25f19d51d821), UINT64_C(0xd80c07cd676f8394), UINT64_C(0x9afce626ce85b507)
|
||||
};
|
||||
|
||||
|
||||
EASTDC_API uint64_t CRC64(const void* pData, size_t nLength, uint64_t nInitialValue, bool bFinalize)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
const uint8_t* const pData8End = pData8 + nLength;
|
||||
|
||||
while(pData8 < pData8End)
|
||||
{
|
||||
const size_t nTableIndex = ((size_t)(nInitialValue >> 56) ^ *pData8++) & 0xff;
|
||||
nInitialValue = crc64Table[nTableIndex] ^ (nInitialValue << 8);
|
||||
}
|
||||
|
||||
if(bFinalize)
|
||||
nInitialValue ^= UINT64_C(0xffffffffffffffff);
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/EAHashString.h>
|
||||
#include <EAStdC/EACType.h>
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DJB2
|
||||
//
|
||||
// This function is deprecated, as FNV1 has been shown to be superior.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API uint32_t DJB2(const void* pData, size_t nLength, uint32_t nInitialValue)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
const uint8_t* const pData8End = pData8 + nLength;
|
||||
|
||||
while(pData8 < pData8End)
|
||||
nInitialValue = ((nInitialValue << 5) + nInitialValue) + *pData8++;
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API uint32_t DJB2_String8(const char* pData8, uint32_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint32_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = ((nInitialValue << 5) + nInitialValue) + c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = ((nInitialValue << 5) + nInitialValue) + Tolower((char)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = ((nInitialValue << 5) + nInitialValue) + Toupper((char)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API uint32_t DJB2_String16(const char16_t* pData16, uint32_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint32_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = ((nInitialValue << 5) + nInitialValue) + c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = ((nInitialValue << 5) + nInitialValue) + Tolower((char16_t)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = ((nInitialValue << 5) + nInitialValue) + Toupper((char16_t)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// FNV1
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API uint32_t FNV1(const void* pData, size_t nLength, uint32_t nInitialValue)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
const uint8_t* const pData8End = pData8 + nLength;
|
||||
|
||||
while(pData8 < pData8End)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ *pData8++;
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API uint32_t FNV1_String8(const char* pData8, uint32_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint32_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ Tolower((char)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ Toupper((char)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API uint32_t FNV1_String16(const char16_t* pData16, uint32_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint32_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ Tolower((char16_t)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ Toupper((char16_t)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
EASTDC_API uint32_t FNV1_String32(const char32_t* pData32, uint32_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint32_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint32_t)*pData32++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint32_t)*pData32++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ Tolower((char32_t)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint32_t)*pData32++) != 0)
|
||||
nInitialValue = (nInitialValue * 16777619) ^ Toupper((char32_t)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API uint64_t FNV64(const void* pData, size_t nLength, uint64_t nInitialValue)
|
||||
{
|
||||
const uint8_t* pData8 = (const uint8_t*)pData;
|
||||
const uint8_t* const pData8End = pData8 + nLength;
|
||||
|
||||
while(pData8 < pData8End)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ *pData8++;
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API uint64_t FNV64_String8(const char* pData8, uint64_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint64_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ Tolower((char)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint8_t)*pData8++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ Toupper((char)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API uint64_t FNV64_String16(const char16_t* pData16, uint64_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint64_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ Tolower((char16_t)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint16_t)*pData16++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ Toupper((char16_t)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
EASTDC_API uint64_t FNV64_String32(const char32_t* pData32, uint64_t nInitialValue, CharCase charCase)
|
||||
{
|
||||
uint64_t c;
|
||||
|
||||
switch (charCase)
|
||||
{
|
||||
case kCharCaseAny:
|
||||
{
|
||||
while((c = (uint32_t)*pData32++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ c;
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseLower:
|
||||
{
|
||||
while((c = (uint32_t)*pData32++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ Tolower((char32_t)c);
|
||||
break;
|
||||
}
|
||||
|
||||
case kCharCaseUpper:
|
||||
{
|
||||
while((c = (uint32_t)*pData32++) != 0)
|
||||
nInitialValue = (nInitialValue * UINT64_C(1099511628211)) ^ Toupper((char32_t)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nInitialValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,431 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// This file implements a basic set of random number generators suitable for game
|
||||
// development usage.
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/EARandom.h>
|
||||
#include <EAStdC/EARandomDistribution.h>
|
||||
#include <EAStdC/EAStopwatch.h>
|
||||
#include <string.h>
|
||||
#include <EAAssert/eaassert.h>
|
||||
|
||||
|
||||
#if defined(EASTDC_EASTOPWATCH_H)
|
||||
#define EARandomGetCPUCycle EA::StdC::Stopwatch::GetCPUCycle
|
||||
#elif EASTDC_TIME_H_AVAILABLE
|
||||
#include <time.h>
|
||||
|
||||
static inline uint64_t EARandomGetCPUCycle()
|
||||
{
|
||||
return (uint64_t)clock();
|
||||
}
|
||||
#else
|
||||
#error Must define a way to get some pseudorandom bits with EARandomGetCPUCycle.
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
// this constant is designed to produce a maximum double value that when cast to a float does not fall outside the
|
||||
// valid range of [0..1).
|
||||
static const float_t RAND_FLOAT_MAX = 1.0f - 1.0f / 1048576.0f; // 1 - 2^-20
|
||||
}
|
||||
|
||||
EASTDC_API void GetRandomSeed(void* pSeed, size_t nLength)
|
||||
{
|
||||
// We get a 64 bit value to work with and copy it repeatedly into
|
||||
// the bytes of the seed.
|
||||
const uint64_t nSeed64 = EARandomGetCPUCycle();
|
||||
|
||||
for(size_t i = 0; i < nLength; i++)
|
||||
((unsigned char*)pSeed)[i] = (unsigned char)(nSeed64 >> ((i % sizeof(uint64_t)) * sizeof(uint64_t)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RandomLinearCongruential
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RandomLinearCongruential::SetSeed(uint32_t nSeed)
|
||||
{
|
||||
if(nSeed == 0xffffffff)
|
||||
nSeed = (uint32_t)(EARandomGetCPUCycle() & 0xffffffff);
|
||||
else if(nSeed == 0) // Test for seed == 0 because that's an illegal value for us.
|
||||
nSeed = 0xaaaaaaaa; // Convert it to some other constant. The actual value of the constant doesn't matter much.
|
||||
|
||||
mnSeed = nSeed;
|
||||
}
|
||||
|
||||
|
||||
uint32_t RandomLinearCongruential::RandomUint32Uniform(uint32_t nLimit)
|
||||
{
|
||||
return EA::StdC::RandomLimit(*this, nLimit);
|
||||
}
|
||||
|
||||
|
||||
double RandomLinearCongruential::RandomDoubleUniform()
|
||||
{
|
||||
// All powers of two (such as this) are exact in floating point
|
||||
static const double kDoubleUniformScaleFactor = 2.32830643653870e-10f; // = (1 / 4294967296)
|
||||
|
||||
|
||||
// Unsigned conversions to float are often slow in due to store-to-load
|
||||
// mismatch stalls (well, at least on some architectures), so we do a
|
||||
// signed conversion.
|
||||
int32_t randInt = int32_t(RandomUint32Uniform());
|
||||
double dResult = (kDoubleUniformScaleFactor * randInt) + 0.5;
|
||||
|
||||
if(dResult > Internal::RAND_FLOAT_MAX) // Due to precision issues, we need to clamp this.
|
||||
dResult = Internal::RAND_FLOAT_MAX;
|
||||
|
||||
return dResult;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RandomTaus
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// P. L'Ecuyer, "Maximally Equidistributed Combined Tausworthe Generators",
|
||||
// Mathematics of Computation, 65, 213 (1996), 203-213.
|
||||
//
|
||||
// This generator has a period of approximately 2^88. This should be
|
||||
// preferred over simple linear congruential generators which fail to
|
||||
// produce uniformly distributed k-tuplets of numbers.
|
||||
//
|
||||
// Approved for EA use by EA Legal:
|
||||
// http://easites.ea.com/legal/Lists/OpenSourceDealSheet/DispForm.aspx?ID=589
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const uint32_t kTausSeed0 = UINT32_C(3719485138);
|
||||
const uint32_t kTausSeed1 = UINT32_C(840184915);
|
||||
const uint32_t kTausSeed2 = UINT32_C(2586639250);
|
||||
|
||||
uint32_t RandomTaus::GetSeed() const
|
||||
{
|
||||
return (mState[0] ^ kTausSeed0);
|
||||
}
|
||||
|
||||
void RandomTaus::SetSeed(uint32_t nSeed)
|
||||
{
|
||||
if(nSeed == 0xffffffff)
|
||||
nSeed = (uint32_t)(EARandomGetCPUCycle() & 0xffffffff);
|
||||
|
||||
const uint32_t newState[3] = { kTausSeed0 ^ nSeed, kTausSeed1 ^ nSeed, kTausSeed2 ^ nSeed };
|
||||
SetSeed(newState);
|
||||
}
|
||||
|
||||
void RandomTaus::SetSeed(const uint32_t* pSeedArray)
|
||||
{
|
||||
if(pSeedArray)
|
||||
{
|
||||
mState[0] = pSeedArray[0];
|
||||
mState[1] = pSeedArray[1];
|
||||
mState[2] = pSeedArray[2];
|
||||
|
||||
if (mState[0] < 2)
|
||||
mState[0] += kTausSeed0; // bad seed -- fix it
|
||||
|
||||
if (mState[1] < 8)
|
||||
mState[1] += kTausSeed1; // bad seed -- fix it
|
||||
|
||||
if (mState[2] < 16)
|
||||
mState[2] += kTausSeed2; // bad seed -- fix it
|
||||
}
|
||||
else
|
||||
SetSeed(0xffffffff); // Set seed automatically.
|
||||
}
|
||||
|
||||
uint32_t RandomTaus::RandomUint32Uniform()
|
||||
{
|
||||
mState[0] = ((mState[0] & 0xfffffffe) << 12) ^ (((mState[0] << 13) ^ mState[0]) >> 19);
|
||||
mState[1] = ((mState[1] & 0xfffffff8) << 4) ^ (((mState[1] << 2) ^ mState[1]) >> 25);
|
||||
mState[2] = ((mState[2] & 0xfffffff0) << 17) ^ (((mState[2] << 3) ^ mState[2]) >> 11);
|
||||
|
||||
return (mState[0] ^ mState[1] ^ mState[2]);
|
||||
}
|
||||
|
||||
uint32_t RandomTaus::RandomUint32Uniform(uint32_t nLimit)
|
||||
{
|
||||
return EA::StdC::RandomLimit(*this, nLimit);
|
||||
}
|
||||
|
||||
double RandomTaus::RandomDoubleUniform()
|
||||
{
|
||||
const uint32_t nRandNoLimit = RandomUint32Uniform();
|
||||
|
||||
// All powers of two (such as this) are exact in floating point
|
||||
static const float kDoubleUniformScaleFactor = 2.32830643653870e-10f;
|
||||
|
||||
// Unsigned conversions to float are often slow in due to store-to-load
|
||||
// mismatch stalls (well, at least on some architectures), so we do a
|
||||
// signed conversion.
|
||||
double dResult = (kDoubleUniformScaleFactor * (int32_t)nRandNoLimit) + 0.5;
|
||||
|
||||
if(dResult > Internal::RAND_FLOAT_MAX) // Due to precision issues, we need to clamp this.
|
||||
dResult = Internal::RAND_FLOAT_MAX;
|
||||
|
||||
return dResult;
|
||||
}
|
||||
|
||||
|
||||
double RandomTaus::RandomDoubleUniform(double limit)
|
||||
{
|
||||
EA_ASSERT(limit > 0);
|
||||
|
||||
const uint32_t nRandNoLimit = RandomUint32Uniform();
|
||||
|
||||
// All powers of two (such as this) are exact in floating point
|
||||
static const float kDoubleUniformScaleFactor = 2.32830643653870e-10f;
|
||||
|
||||
// Unsigned conversions to float are often slow in due to store-to-load
|
||||
// mismatch stalls (well, at least on some architectures), so we do a
|
||||
// signed conversion.
|
||||
double dResult = (kDoubleUniformScaleFactor * limit * (int32_t)nRandNoLimit) + 0.5;
|
||||
|
||||
if(dResult > Internal::RAND_FLOAT_MAX) // Due to precision issues, we need to clamp this.
|
||||
dResult = Internal::RAND_FLOAT_MAX;
|
||||
|
||||
return dResult;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RandomMersenneTwister
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
RandomMersenneTwister::RandomMersenneTwister(uint32_t nSeed)
|
||||
{
|
||||
mpNextState = NULL;
|
||||
mnCountRemaining = kStateCount;
|
||||
SetSeed(nSeed);
|
||||
}
|
||||
|
||||
|
||||
RandomMersenneTwister::RandomMersenneTwister(const uint32_t seedArray[], unsigned nSeedArraySize)
|
||||
{
|
||||
mpNextState = NULL;
|
||||
mnCountRemaining = kStateCount;
|
||||
SetSeed(seedArray, nSeedArraySize);
|
||||
}
|
||||
|
||||
|
||||
RandomMersenneTwister& RandomMersenneTwister::operator=(const RandomMersenneTwister& randomMT)
|
||||
{
|
||||
::memcpy(mState, randomMT.mState, sizeof(mState));
|
||||
mpNextState = &mState[0] + (randomMT.mpNextState - randomMT.mState);
|
||||
mnCountRemaining = randomMT.mnCountRemaining;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
#define LOCAL_MIN(x, y) (x) < (y) ? (x) : (y)
|
||||
|
||||
|
||||
unsigned RandomMersenneTwister::GetSeed(uint32_t seedArray[], unsigned nSeedArraySize) const
|
||||
{
|
||||
if(nSeedArraySize >= 1)
|
||||
{
|
||||
// Get mnCountRemaining
|
||||
seedArray[0] = (uint32_t)mnCountRemaining;
|
||||
|
||||
// Get mState
|
||||
unsigned i, copyCount = LOCAL_MIN((unsigned)kStateCount, nSeedArraySize - 1);
|
||||
|
||||
for(i = 0; i < copyCount; i++)
|
||||
seedArray[i + 1] = mState[i];
|
||||
|
||||
for(i = copyCount; i < (nSeedArraySize - 1); i++)
|
||||
seedArray[i + 1] = 0;
|
||||
|
||||
return copyCount + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void RandomMersenneTwister::SetSeed(const uint32_t seedArray[], unsigned nSeedArraySize)
|
||||
{
|
||||
if(nSeedArraySize >= 1)
|
||||
{
|
||||
// Set mnCountRemaining
|
||||
mnCountRemaining = (int32_t)seedArray[0];
|
||||
if(mnCountRemaining > kStateCount)
|
||||
mnCountRemaining = kStateCount;
|
||||
|
||||
// Set mpNextState
|
||||
mpNextState = mState + (kStateCount - mnCountRemaining);
|
||||
|
||||
// Set mState
|
||||
const uint32_t* pStateInput = seedArray + 1; // +1 because seedArray[0] stores mnCountRemaining.
|
||||
uint32_t* pStateOutput = &mState[0];
|
||||
uint32_t* pStateOutputEnd = pStateOutput + kStateCount;
|
||||
|
||||
while(pStateOutput < pStateOutputEnd)
|
||||
{
|
||||
if(pStateInput >= (seedArray + 1 + nSeedArraySize))
|
||||
pStateInput = (seedArray + 1); // Go back to the beginning.
|
||||
|
||||
*pStateOutput++ = *pStateInput++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RandomMersenneTwister::SetSeed(uint32_t nSeed)
|
||||
{
|
||||
uint32_t* pState = &mState[0];
|
||||
int i = kStateCount;
|
||||
|
||||
if(nSeed == 0xffffffff)
|
||||
nSeed = (uint32_t)(EARandomGetCPUCycle() & 0xffffffff);
|
||||
|
||||
// Even seeds for the Mersenne Twister are known to be bad,
|
||||
// where bad means a non-maximal period and striping.
|
||||
nSeed |= 1;
|
||||
|
||||
while(i--)
|
||||
{
|
||||
*pState = nSeed & 0xffff0000;
|
||||
*pState |= ((nSeed *= 69069)++ & 0xffff0000) >> 16;
|
||||
pState++;
|
||||
(nSeed *= 69069)++;
|
||||
}
|
||||
Reload();
|
||||
}
|
||||
|
||||
|
||||
uint32_t RandomMersenneTwister::RandomUint32Uniform()
|
||||
{
|
||||
uint32_t nValue;
|
||||
|
||||
if(--mnCountRemaining < 0)
|
||||
{
|
||||
Reload();
|
||||
--mnCountRemaining;
|
||||
}
|
||||
|
||||
nValue = *mpNextState++;
|
||||
nValue ^= (nValue >> 11);
|
||||
nValue ^= (nValue << 7) & 0x9d2c5680;
|
||||
nValue ^= (nValue << 15) & 0xefc60000;
|
||||
return nValue ^ (nValue >> 18);
|
||||
}
|
||||
|
||||
|
||||
uint32_t RandomMersenneTwister::RandomUint32Uniform(uint32_t nLimit)
|
||||
{
|
||||
return EA::StdC::RandomLimit(*this, nLimit);
|
||||
}
|
||||
|
||||
|
||||
double RandomMersenneTwister::RandomDoubleUniform()
|
||||
{
|
||||
double dResult = (int32_t)RandomUint32Uniform() * 2.3283064365386963e-10 + 0.5;
|
||||
|
||||
if(dResult > Internal::RAND_FLOAT_MAX) // Due to precision issues, we need to clamp this.
|
||||
dResult = Internal::RAND_FLOAT_MAX;
|
||||
|
||||
return dResult;
|
||||
}
|
||||
|
||||
|
||||
static inline uint32_t LoBit(uint32_t n)
|
||||
{
|
||||
return (n & 0x00000001);
|
||||
}
|
||||
|
||||
|
||||
static inline uint32_t MixBits(uint32_t n, uint32_t m)
|
||||
{
|
||||
return ((n & 0x80000000) | (m & 0x7FFFFFFF));
|
||||
}
|
||||
|
||||
|
||||
void RandomMersenneTwister::Reload()
|
||||
{
|
||||
const uint32_t kMagicNumber = 0x9908b0df; // Needs to be used as unsigned.
|
||||
const int kPeriodValue = 397;
|
||||
uint32_t *p0 = &mState[0], *p2 = &mState[1], *pM = &mState[kPeriodValue];
|
||||
uint32_t s0 = mState[0], s1 = mState[1];
|
||||
int i;
|
||||
|
||||
for(i = kStateCount - kPeriodValue; i--; s0 = s1, s1 = *++p2)
|
||||
*p0++ = *pM++ ^ (MixBits(s0, s1) >> 1) ^ (LoBit(s1) ? kMagicNumber : 0);
|
||||
|
||||
for(pM = &mState[0], i = kPeriodValue; --i; s0 = s1, s1 = *++p2 )
|
||||
*p0++ = *pM++ ^ (MixBits(s0, s1) >> 1) ^ (LoBit(s1) ? kMagicNumber : 0);
|
||||
|
||||
s1 = mState[0];
|
||||
*p0 = *pM ^ (MixBits(s0, s1) >> 1) ^ (LoBit(s1) ? kMagicNumber : 0);
|
||||
|
||||
mnCountRemaining = kStateCount;
|
||||
mpNextState = &mState[0];
|
||||
}
|
||||
|
||||
|
||||
uint32_t RandomMersenneTwister::Hash(int t, int c)
|
||||
{
|
||||
static uint32_t nIncrementor = 0;
|
||||
|
||||
uint32_t h1 = 0;
|
||||
uint32_t h2 = 0;
|
||||
unsigned char* p = (unsigned char*)&t;
|
||||
unsigned i;
|
||||
|
||||
for(i=0; i < sizeof(t); ++i)
|
||||
{
|
||||
h1 *= UINT8_MAX + 2;
|
||||
h1 += p[i];
|
||||
}
|
||||
|
||||
p = (unsigned char*)&c;
|
||||
|
||||
for(i=0; i < sizeof(c); ++i)
|
||||
{
|
||||
h2 *= UINT8_MAX + 2;
|
||||
h2 += p[i];
|
||||
}
|
||||
|
||||
return (h1 + nIncrementor++) ^ h2;
|
||||
}
|
||||
|
||||
|
||||
// For unity build friendliness, undef all local #defines.
|
||||
#undef EARandomGetCPUCycle
|
||||
#undef LOCAL_MIN
|
||||
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/EAScanf.h>
|
||||
#include <EAStdC/internal/ScanfCore.h>
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char functions
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int Cscanf(ReadFunction8 pReadFunction8, void* pContext, const char* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(pReadFunction8, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Fscanf(FILE* pFile, const char* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader8, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Scanf(const char* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader8, stdin, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Sscanf(const char* pDestination, const char* pFormat, ...)
|
||||
{
|
||||
ScanfLocal::SscanfContext8 sc(pDestination);
|
||||
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::StringReader8, &sc, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API int Vcscanf(ReadFunction8 pReadFunction8, void* pContext, const char* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(pReadFunction8, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vfscanf(FILE* pFile, const char* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader8, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vscanf(const char* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader8, stdin, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsscanf(const char* pDestination, const char* pFormat, va_list arguments)
|
||||
{
|
||||
ScanfLocal::SscanfContext8 sc(pDestination);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::StringReader8, &sc, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char16_t functions
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int Cscanf(ReadFunction16 pReadFunction16, void* pContext, const char16_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(pReadFunction16, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Fscanf(FILE* pFile, const char16_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader16, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Scanf(const char16_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader16, stdin, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Sscanf(const char16_t* pDestination, const char16_t* pFormat, ...)
|
||||
{
|
||||
ScanfLocal::SscanfContext16 sc(pDestination);
|
||||
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::StringReader16, &sc, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API int Vcscanf(ReadFunction16 pReadFunction16, void* pContext, const char16_t* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(pReadFunction16, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vfscanf(FILE* pFile, const char16_t* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader16, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vscanf(const char16_t* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader16, stdin, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsscanf(const char16_t* pDestination, const char16_t* pFormat, va_list arguments)
|
||||
{
|
||||
ScanfLocal::SscanfContext16 sc(pDestination);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::StringReader16, &sc, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char32_t functions
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int Cscanf(ReadFunction32 pReadFunction32, void* pContext, const char32_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(pReadFunction32, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Fscanf(FILE* pFile, const char32_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader32, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Scanf(const char32_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader32, stdin, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Sscanf(const char32_t* pDestination, const char32_t* pFormat, ...)
|
||||
{
|
||||
ScanfLocal::SscanfContext32 sc(pDestination);
|
||||
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::StringReader32, &sc, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
EASTDC_API int Vcscanf(ReadFunction32 pReadFunction32, void* pContext, const char32_t* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(pReadFunction32, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vfscanf(FILE* pFile, const char32_t* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader32, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vscanf(const char32_t* pFormat, va_list arguments)
|
||||
{
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::FILEReader32, stdin, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsscanf(const char32_t* pDestination, const char32_t* pFormat, va_list arguments)
|
||||
{
|
||||
ScanfLocal::SscanfContext32 sc(pDestination);
|
||||
|
||||
return ScanfLocal::VscanfCore(ScanfLocal::StringReader32, &sc, pFormat, arguments);
|
||||
}
|
||||
|
||||
#if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE
|
||||
EASTDC_API int Cscanf(ReadFunctionW pReadFunctionW, void* pContext, const wchar_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vcscanf(pReadFunctionW, pContext, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Fscanf(FILE* pFile, const wchar_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vfscanf(pFile, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Scanf(const wchar_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vscanf(pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Sscanf(const wchar_t* pTextBuffer, const wchar_t* pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsscanf(pTextBuffer, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Vcscanf(ReadFunctionW pReadFunctionW, void* pContext, const wchar_t* pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vcscanf(reinterpret_cast<ReadFunction16>(pReadFunctionW), pContext, reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vcscanf(reinterpret_cast<ReadFunction32>(pReadFunctionW), pContext, reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vfscanf(FILE* pFile, const wchar_t* pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vfscanf(pFile, reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vfscanf(pFile, reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vscanf(const wchar_t* pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vscanf(reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vscanf(reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vsscanf(const wchar_t* pTextBuffer, const wchar_t* pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vsscanf(reinterpret_cast<const char16_t *>(pTextBuffer), reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vsscanf(reinterpret_cast<const char32_t *>(pTextBuffer), reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,603 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/internal/SprintfCore.h>
|
||||
#include <EAStdC/EAString.h>
|
||||
#include <EAStdC/EACType.h>
|
||||
#include <EAAssert/eaassert.h>
|
||||
EA_DISABLE_ALL_VC_WARNINGS()
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
EA_RESTORE_ALL_VC_WARNINGS()
|
||||
EA_DISABLE_VC_WARNING(4127) // conditional expression is constant.
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int Vcprintf(WriteFunction8 pWriteFunction8, void* EA_RESTRICT pContext, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::VprintfCore(pWriteFunction8, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vfprintf(FILE* EA_RESTRICT pFile, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if EASTDC_PRINTF_DEBUG_ENABLED
|
||||
if((pFile == stdout) || (pFile == stderr))
|
||||
{
|
||||
SprintfLocal::PlatformLogWriterContext8 context;
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::PlatformLogWriter8, &context, pFormat, arguments);
|
||||
}
|
||||
#endif
|
||||
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::FILEWriter8, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vprintf(const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if EASTDC_PRINTF_DEBUG_ENABLED
|
||||
SprintfLocal::PlatformLogWriterContext8 context;
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::PlatformLogWriter8, &context, pFormat, arguments);
|
||||
#else
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::FILEWriter8, stdout, pFormat, arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vsprintf(char* EA_RESTRICT pDestination, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Vsnprintf(pDestination, (size_t)-1, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsnprintf(char* EA_RESTRICT pDestination, size_t n, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
SprintfLocal::SnprintfContext8 sc(pDestination, 0, pDestination ? n : 0);
|
||||
|
||||
const int nRequiredLength = SprintfLocal::VprintfCore(SprintfLocal::StringWriter8, &sc, pFormat, arguments);
|
||||
|
||||
#if EASPRINTF_SNPRINTF_C99_RETURN
|
||||
if(pDestination && (nRequiredLength >= 0))
|
||||
{
|
||||
if((size_t)nRequiredLength < n) // If there was enough space...
|
||||
pDestination[nRequiredLength] = 0;
|
||||
else if(n > 0)
|
||||
pDestination[n - 1] = 0;
|
||||
} // Else an encoding error has occurred and we can do nothing.
|
||||
|
||||
return nRequiredLength;
|
||||
#else
|
||||
if((size_t)nRequiredLength < n)
|
||||
{
|
||||
if(pDestination)
|
||||
pDestination[nRequiredLength] = 0;
|
||||
return nRequiredLength;
|
||||
}
|
||||
else if((n > 0) && pDestination)
|
||||
pDestination[n - 1] = 0;
|
||||
else if(n == 0)
|
||||
return nRequiredLength;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vscprintf(const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
// vscprintf returns the number of chars that are needed for a printf operation.
|
||||
return Vsnprintf(NULL, 0, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vdprintf(const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
SprintfLocal::PlatformLogWriterContext8 context;
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::PlatformLogWriter8, &context, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
EASTDC_API int Cprintf(WriteFunction8 pWriteFunction, void* EA_RESTRICT pContext, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = SprintfLocal::VprintfCore(pWriteFunction, pContext, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Fprintf(FILE* EA_RESTRICT pFile, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
int result;
|
||||
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
#if EASTDC_PRINTF_DEBUG_ENABLED
|
||||
if((pFile == stdout) || (pFile == stderr))
|
||||
{
|
||||
SprintfLocal::PlatformLogWriterContext8 context;
|
||||
result = SprintfLocal::VprintfCore(SprintfLocal::PlatformLogWriter8, &context, pFormat, arguments);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
result = SprintfLocal::VprintfCore(SprintfLocal::FILEWriter8, pFile, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Printf(const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
#if EASTDC_PRINTF_DEBUG_ENABLED
|
||||
SprintfLocal::PlatformLogWriterContext8 context;
|
||||
int result = SprintfLocal::VprintfCore(SprintfLocal::PlatformLogWriter8, &context, pFormat, arguments);
|
||||
#else
|
||||
int result = SprintfLocal::VprintfCore(SprintfLocal::FILEWriter8, stdout, pFormat, arguments);
|
||||
#endif
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Sprintf(char* EA_RESTRICT pDestination, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsnprintf(pDestination, (size_t)SprintfLocal::kNoPrecision, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Snprintf(char* EA_RESTRICT pDestination, size_t n, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Dprintf(const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
SprintfLocal::PlatformLogWriterContext8 context;
|
||||
int result = SprintfLocal::VprintfCore(SprintfLocal::PlatformLogWriter8, &context, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char16_t
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int Vcprintf(WriteFunction16 pWriteFunction16, void* EA_RESTRICT pContext, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::VprintfCore(pWriteFunction16, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vfprintf(FILE* EA_RESTRICT pFile, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::FILEWriter16, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vprintf(const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::FILEWriter16, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsprintf(char16_t* EA_RESTRICT pDestination, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Vsnprintf(pDestination, (size_t)-1, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsnprintf(char16_t* EA_RESTRICT pDestination, size_t n, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
SprintfLocal::SnprintfContext16 sc(pDestination, 0, pDestination ? n : 0);
|
||||
|
||||
const int nRequiredLength = SprintfLocal::VprintfCore(SprintfLocal::StringWriter16, &sc, pFormat, arguments);
|
||||
|
||||
#if EASPRINTF_SNPRINTF_C99_RETURN
|
||||
if(pDestination && (nRequiredLength >= 0))
|
||||
{
|
||||
if((size_t)nRequiredLength < n) // If there was enough space...
|
||||
pDestination[nRequiredLength] = 0;
|
||||
else if(n > 0)
|
||||
pDestination[n - 1] = 0;
|
||||
} // Else an encoding error has occurred and we can do nothing.
|
||||
|
||||
return nRequiredLength;
|
||||
#else
|
||||
if((size_t)nRequiredLength < n)
|
||||
{
|
||||
if(pDestination)
|
||||
pDestination[nRequiredLength] = 0;
|
||||
return nRequiredLength;
|
||||
}
|
||||
else if((n > 0) && pDestination)
|
||||
pDestination[n - 1] = 0;
|
||||
else if(n == 0)
|
||||
return nRequiredLength;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vscprintf(const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
// vscprintf returns the number of chars that are needed for a printf operation.
|
||||
return Vsnprintf(NULL, 0, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Cprintf(WriteFunction16 pWriteFunction, void* EA_RESTRICT pContext, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = SprintfLocal::VprintfCore(pWriteFunction, pContext, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Fprintf(FILE* EA_RESTRICT pFile, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = SprintfLocal::VprintfCore(SprintfLocal::FILEWriter16, pFile, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Printf(const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = SprintfLocal::VprintfCore(SprintfLocal::FILEWriter16, stdout, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Sprintf(char16_t* EA_RESTRICT pDestination, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsnprintf(pDestination, (size_t)SprintfLocal::kNoPrecision, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Snprintf(char16_t* EA_RESTRICT pDestination, size_t n, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char32_t
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int Vcprintf(WriteFunction32 pWriteFunction32, void* EA_RESTRICT pContext, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::VprintfCore(pWriteFunction32, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vfprintf(FILE* EA_RESTRICT pFile, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::FILEWriter32, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vprintf(const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::VprintfCore(SprintfLocal::FILEWriter32, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsprintf(char32_t* EA_RESTRICT pDestination, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Vsnprintf(pDestination, (size_t)-1, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsnprintf(char32_t* EA_RESTRICT pDestination, size_t n, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
SprintfLocal::SnprintfContext32 sc(pDestination, 0, pDestination ? n : 0);
|
||||
|
||||
const int nRequiredLength = SprintfLocal::VprintfCore(SprintfLocal::StringWriter32, &sc, pFormat, arguments);
|
||||
|
||||
#if EASPRINTF_SNPRINTF_C99_RETURN
|
||||
if(pDestination && (nRequiredLength >= 0))
|
||||
{
|
||||
if((size_t)nRequiredLength < n) // If there was enough space...
|
||||
pDestination[nRequiredLength] = 0;
|
||||
else if(n > 0)
|
||||
pDestination[n - 1] = 0;
|
||||
} // Else an encoding error has occurred and we can do nothing.
|
||||
|
||||
return nRequiredLength;
|
||||
#else
|
||||
if((size_t)nRequiredLength < n)
|
||||
{
|
||||
if(pDestination)
|
||||
pDestination[nRequiredLength] = 0;
|
||||
return nRequiredLength;
|
||||
}
|
||||
else if((n > 0) && pDestination)
|
||||
pDestination[n - 1] = 0;
|
||||
else if(n == 0)
|
||||
return nRequiredLength;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vscprintf(const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
// vscprintf returns the number of chars that are needed for a printf operation.
|
||||
return Vsnprintf(NULL, 0, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Cprintf(WriteFunction32 pWriteFunction, void* EA_RESTRICT pContext, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = SprintfLocal::VprintfCore(pWriteFunction, pContext, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Fprintf(FILE* EA_RESTRICT pFile, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = SprintfLocal::VprintfCore(SprintfLocal::FILEWriter32, pFile, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Printf(const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = SprintfLocal::VprintfCore(SprintfLocal::FILEWriter32, stdout, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Sprintf(char32_t* EA_RESTRICT pDestination, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsnprintf(pDestination, (size_t)SprintfLocal::kNoPrecision, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Snprintf(char32_t* EA_RESTRICT pDestination, size_t n, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Deprecated functionality
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if EASTDC_VSNPRINTF8_ENABLED
|
||||
EASTDC_API int Vsnprintf8(char* EA_RESTRICT pDestination, size_t n, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsnprintf16(char16_t* EA_RESTRICT pDestination, size_t n, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int Vsnprintf32(char32_t* EA_RESTRICT pDestination, size_t n, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
}
|
||||
|
||||
#if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE
|
||||
EASTDC_API int VsnprintfW(wchar_t* EA_RESTRICT pDestination, size_t n, const wchar_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE
|
||||
EASTDC_API int Vcprintf(WriteFunctionW pWriteFunctionW, void* EA_RESTRICT pContext, const wchar_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vcprintf(reinterpret_cast<WriteFunction16>(pWriteFunctionW), pContext, reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vcprintf(reinterpret_cast<WriteFunction32>(pWriteFunctionW), pContext, reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vfprintf(FILE* EA_RESTRICT pFile, const wchar_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vfprintf(pFile, reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vfprintf(pFile, reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vprintf(const wchar_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vprintf(reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vprintf(reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vsprintf(wchar_t* EA_RESTRICT pDestination, const wchar_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vsprintf(reinterpret_cast<char16_t *>(pDestination), reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vsprintf(reinterpret_cast<char32_t *>(pDestination), reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vsnprintf(wchar_t* EA_RESTRICT pDestination, size_t n, const wchar_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vsnprintf(reinterpret_cast<char16_t *>(pDestination), n, reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vsnprintf(reinterpret_cast<char32_t *>(pDestination), n, reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Vscprintf(const wchar_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
#if (EA_WCHAR_SIZE == 2)
|
||||
return Vscprintf(reinterpret_cast<const char16_t *>(pFormat), arguments);
|
||||
#else
|
||||
return Vscprintf(reinterpret_cast<const char32_t *>(pFormat), arguments);
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int Cprintf(WriteFunctionW pWriteFunction, void* EA_RESTRICT pContext, const wchar_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vcprintf(pWriteFunction, pContext, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Fprintf(FILE* EA_RESTRICT pFile, const wchar_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vfprintf(pFile, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Printf(const wchar_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vprintf(pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Sprintf(wchar_t* EA_RESTRICT pDestination, const wchar_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsprintf(pDestination, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EASTDC_API int Snprintf(wchar_t* EA_RESTRICT pDestination, size_t n, const wchar_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
int result = Vsnprintf(pDestination, n, pFormat, arguments);
|
||||
|
||||
va_end(arguments);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
|
||||
|
||||
EA_RESTORE_VC_WARNING()
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,777 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <EAStdC/internal/Config.h>
|
||||
#include <EAStdC/internal/SprintfCore.h>
|
||||
#include <EAStdC/EASprintfOrdered.h>
|
||||
#include <EAStdC/EASprintf.h>
|
||||
#include <EAStdC/EABitTricks.h>
|
||||
#include <EAAssert/eaassert.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
// GCC has va_copy, but VC++ does not. However, VC++ implements
|
||||
// va_list in such a way that it can simply be memcpyd.
|
||||
#ifndef va_copy
|
||||
#ifdef __va_copy
|
||||
#define va_copy(dest, src) __va_copy((dest), (src))
|
||||
#else
|
||||
#define va_copy(dest, src) memcpy(&(dest), &(src), sizeof(va_list))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
namespace SprintfLocal
|
||||
{
|
||||
|
||||
static const int kSpanFormatCapacity = 16;
|
||||
|
||||
template<typename CharT>
|
||||
struct Span
|
||||
{
|
||||
const CharT* mpBegin; // The first char in the span.
|
||||
const CharT* mpEnd; // One-past the last used char.
|
||||
Modifier mType; // This tells us what the type of the argument is (e.g. kModifierInt).
|
||||
AllTypes mValue; // This stores the value, which is of type mType.
|
||||
CharT mFormat[kSpanFormatCapacity]; // The format to use (e.g. %5.3f). If empty then this is a string span.
|
||||
CharT mFormatChar; // The last char in the mFormat string.
|
||||
int mUserIndex; // The index the user assigned to this format. Negative value if this is a string span.
|
||||
bool mbEscapePresent; // True if the span is a string and it has a %% sequence. We can optimize writes if we know that it doesn't.
|
||||
|
||||
Span() : mpBegin(nullptr),
|
||||
mpEnd(nullptr),
|
||||
mType(kModifierNone),
|
||||
mValue(),
|
||||
mFormatChar(0),
|
||||
mUserIndex(0),
|
||||
mbEscapePresent(false)
|
||||
{ mFormat[0] = 0; }
|
||||
};
|
||||
|
||||
using Span8 = Span<char>;
|
||||
using Span16 = Span<char16_t>;
|
||||
using Span32 = Span<char32_t>;
|
||||
|
||||
|
||||
|
||||
// This function exists for the sole purpose of passing an arbitrary argument to VprintfCore
|
||||
// along with an existing WriteFunction8/pWriteFunctionContext8.
|
||||
static int CallVprintfCore(WriteFunction8 pWriteFunction8, void* EA_RESTRICT pWriteFunctionContext8, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return VprintfCore(pWriteFunction8, pWriteFunctionContext8, pFormat, arguments);
|
||||
}
|
||||
|
||||
static int CallVprintfCore(WriteFunction16 pWriteFunction16, void* EA_RESTRICT pWriteFunctionContext16, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return VprintfCore(pWriteFunction16, pWriteFunctionContext16, pFormat, arguments);
|
||||
}
|
||||
|
||||
static int CallVprintfCore(WriteFunction32 pWriteFunction32, void* EA_RESTRICT pWriteFunctionContext32, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return VprintfCore(pWriteFunction32, pWriteFunctionContext32, pFormat, arguments);
|
||||
}
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
// The way this function works is as follows: We walk through the pFormat string and identify all
|
||||
// format spans and non-format spans (i.e. literal text) between format spans. Thus for the string
|
||||
// " %1:f %0:d " we have five spans: three non-format spans, and two format spans. The we
|
||||
// read the va_list arguments in the user-specified order into a union that can hold any type.
|
||||
// It's important that we read the arguments in user-specified order and not format string order.
|
||||
// So the %d would be read first as an int, and the %f would be read second as a double.
|
||||
// Finally we walk through the format in string order and write out each span to the output.
|
||||
// Non-format segments are simply copied to the output. Format segments are written to the output
|
||||
// by a call to VPrintfCore each.
|
||||
//
|
||||
template<typename Span, typename WriteFunction, typename CharT>
|
||||
static int OVprintfCore(WriteFunction pWriteFunction, void* EA_RESTRICT pWriteFunctionContext, const CharT* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
const int kArgCapacity = 10; // Currently only single digit ('0'-'9') order values are supported.
|
||||
const int kSpanCapacity = 21; // Worst case scenario of 21 spans. For example: " %2:d %7:d %1:d %6:d %3:d %5:d %4:d %0:d %8:d %9:d " or "%0:d%1:d%2:d%3:d%4:d%5:d%6:d%7:d%8:d%9:d"
|
||||
Span spans[kSpanCapacity];
|
||||
int spanArgOrder[kArgCapacity] = { -1 }; // Each entry is the index into 'spans' of that argument. This allows us to quickly find formats in the order the user passed them to this function. For the example directly above, the contents would be: 5, 1, 9, 13, 11, 7, 3, 17, 15. We initialize it to -1 in order to avoid compiler warnings about it being used before set.
|
||||
int spanIndex = 0;
|
||||
int formattedSpanCount = 0;
|
||||
bool bInFormat = false; // State variable indicating if we are within a % format sequence.
|
||||
int nFormatLength = 0;
|
||||
int nWriteCountSum = 0;
|
||||
int startIndex = 1; // This is 1 or 0, and it defaults to 1 (but may change below) in order to mean that user formats start at 1, as in "%1:d". However, we have a feature whereby we detect that the user is using %0 as the start index.
|
||||
const CharT* p;
|
||||
const CharT* pEnd;
|
||||
int result;
|
||||
|
||||
static_assert((EAArrayCount(spans) == kSpanCapacity) && (EAArrayCount(spanArgOrder) == kArgCapacity), "spans and spanArgOrder are not using constants for their array size.");
|
||||
|
||||
#ifdef EA_DEBUG
|
||||
for(int s = 0; s < kArgCapacity; ++s)
|
||||
spanArgOrder[s] = -1;
|
||||
#else
|
||||
memset(spanArgOrder, 0, sizeof(spanArgOrder));
|
||||
#endif
|
||||
|
||||
pWriteFunction(NULL, 0, pWriteFunctionContext, kWFSBegin);
|
||||
|
||||
// Initialize the first span. We always have a beginning sequence that is
|
||||
// a string, even if it is empty. Actually, there may be an empty string
|
||||
// span between any two format spans. We'll ignore them later.
|
||||
spans[0].mpBegin = pFormat;
|
||||
spans[0].mUserIndex = -1;
|
||||
|
||||
// Build the list of spans.
|
||||
// Read each format string character while maintaining a little state machine.
|
||||
for(p = pFormat; *p; ++p)
|
||||
{
|
||||
if(*p == '%')
|
||||
{
|
||||
// A % char within a format is invalid (though %% is valid), and any '%' char that
|
||||
// begins a format must be followed by at least three more chars, two for the user
|
||||
// index plus colon and at least one for the actual format (e.g. %4:d).
|
||||
EA_ASSERT(!bInFormat && p[1] && p[2] && p[3]);
|
||||
|
||||
if(p[1] == '%')
|
||||
{
|
||||
spans[spanIndex].mbEscapePresent = true;
|
||||
p++; // Skip past the second % char.
|
||||
}
|
||||
else // else we don't have a %% sequence and thus have the start of a format...
|
||||
{
|
||||
// Finalize the current span (the one before the % char), before starting a new span for this % sequence.
|
||||
spans[spanIndex].mpEnd = p;
|
||||
spans[spanIndex].mFormat[nFormatLength] = 0;
|
||||
spans[spanIndex].mFormatChar = 0; // This is redundant.
|
||||
if(++spanIndex == kSpanCapacity)
|
||||
break;
|
||||
|
||||
// Intialize the next span.
|
||||
if((p[1] < '0') || (p[1] > '9'))
|
||||
return -1; // Invalid format. User specified a format like "%X:d" or just "%"
|
||||
|
||||
const int userIndex = (int)(p[1] - '0');
|
||||
|
||||
if((userIndex == 0) && (startIndex != 0)) // If it appears that the user is using argument numbering that is 0-based (e.g. "%0:d") instead of 1-based...
|
||||
{
|
||||
startIndex = 0;
|
||||
for(int i = kArgCapacity - 1; i > 0; --i)
|
||||
spanArgOrder[i] = spanArgOrder[i - 1]; // Convert any existing indexes from what we originally assumed to be 1-based values 'up' to being 0-based values.
|
||||
}
|
||||
|
||||
bInFormat = true;
|
||||
nFormatLength = 1; // For the % char we write into mFormat.
|
||||
spans[spanIndex].mpBegin = p;
|
||||
spans[spanIndex].mFormat[0] = '%';
|
||||
spans[spanIndex].mUserIndex = userIndex; // We don't write the user index or the colon into the format string, which is a standard C format specifier sequence.
|
||||
spanArgOrder[userIndex - startIndex] = spanIndex; // startIndex is normally 1, because usually users specify argument indexes in a 1:based way.
|
||||
formattedSpanCount++;
|
||||
EA_ASSERT(p[2] == ':'); // We expect formats to have a N: sequence after the % in order to indicate order (e.g. %4:3.1f means %3.1f as 4th argument)
|
||||
if(p[2] != ':')
|
||||
return -1; // Invalid format. User specified a format like "%00:d" or just "%0"
|
||||
p += 2; // Skip past the user index and the colon.
|
||||
}
|
||||
}
|
||||
else if(bInFormat)
|
||||
{
|
||||
EA_ASSERT(nFormatLength < kSpanFormatCapacity);
|
||||
if(nFormatLength < kSpanFormatCapacity)
|
||||
spans[spanIndex].mFormat[nFormatLength++] = *p;
|
||||
else
|
||||
return -1; // Invalid format. User specified a format like "%0:000000000000001d" (too long a printf format)
|
||||
|
||||
switch(*p)
|
||||
{
|
||||
case 'b': case 'd': case 'i': case 'u': case 'o': case 'x': case 'X': // If this character is the last posible character of a format specifier...
|
||||
case 'g': case 'G': case 'e': case 'E': case 'f': case 'F': case 'a':
|
||||
case 'A': case 'p': case 'c': case 'C': case 's': case 'S': case 'n':
|
||||
{
|
||||
// Finalize the current span.
|
||||
spans[spanIndex].mpEnd = p + 1;
|
||||
spans[spanIndex].mFormat[nFormatLength < kSpanFormatCapacity ? nFormatLength : kSpanFormatCapacity - 1] = 0;
|
||||
spans[spanIndex].mFormatChar = *p;
|
||||
if(++spanIndex == kSpanCapacity)
|
||||
break;
|
||||
|
||||
// Intialize the next span.
|
||||
bInFormat = false;
|
||||
nFormatLength = 0;
|
||||
spans[spanIndex].mpBegin = p + 1;
|
||||
spans[spanIndex].mUserIndex = -1; // This is a string span. If a format sequence or end-of-string immediately follows then this will be an empty string span.
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// To do: Handle the case of an invalid format character.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Else we are in the middle of a string portion of the format and we need do nothing here.
|
||||
}
|
||||
|
||||
// Finalize the last span, which by definition ends at the end of our input.
|
||||
EA_ASSERT(spanIndex < kSpanCapacity);
|
||||
if((spanIndex == kSpanCapacity) && *p) // If the user somehow specified more format spans than possible...
|
||||
return -1; // Invalid format. Too many format spans.
|
||||
|
||||
spans[spanIndex].mpEnd = p; // p Should always point to the terminating 0 char of pFormat.
|
||||
spans[spanIndex].mFormat[nFormatLength] = 0;
|
||||
spanIndex++;
|
||||
|
||||
// Now we read the arguments into span[s].mValue in the order they were passed by the caller.
|
||||
for(int i = 0; i < formattedSpanCount; ++i)
|
||||
{
|
||||
EA_ASSERT((spanArgOrder[i] >= 0) && (spanArgOrder[i] < kSpanCapacity));
|
||||
|
||||
Span& span = spans[spanArgOrder[i]];
|
||||
FormatData formatData;
|
||||
|
||||
// We call ReadFormat in order to get the argument type. We don't need the other information from FormatData.
|
||||
// ReadFormat returns the pointer to the next char after the format sequence. If there is an error then it
|
||||
// returns a pointer to where it failed. We can use this to tell if ReadFormat failed by testing *pEnd == 0.
|
||||
pEnd = ReadFormat(span.mFormat, &formatData, (va_list*)&arguments);
|
||||
|
||||
if(*pEnd != 0) // If ReadFormat bailed before processing the entire format...
|
||||
return -1;
|
||||
|
||||
// Unfortunately, ReadFormat tells us the type only if it is a modified type (e.g. %ld as opposed to %d).
|
||||
// We could go and modify ReadFormat to store the full type all the time, but that would be messing with
|
||||
// that function and its performance and we'd rather stay away from that. We can solve this with a simple
|
||||
// switch statement here.
|
||||
if(formatData.mModifier == kModifierNone)
|
||||
{
|
||||
switch (pEnd[-1])
|
||||
{
|
||||
case 'b': case 'd': case 'i': case 'u':
|
||||
case 'o': case 'x': case 'X':
|
||||
formatData.mModifier = kModifierInt;
|
||||
break;
|
||||
|
||||
case 'g': case 'G': case 'e': case 'E':
|
||||
case 'f': case 'F': case 'a': case 'A':
|
||||
formatData.mModifier = kModifierDouble;
|
||||
break;
|
||||
|
||||
case 'p': case 's': case 'S': case 'n':
|
||||
EA_COMPILETIME_ASSERT(sizeof(size_t) == sizeof(void*)); // If this fails then we need to modify this statement.
|
||||
formatData.mModifier = kModifierSize_t;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
formatData.mModifier = kModifierChar;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
formatData.mModifier = kModifierWChar;
|
||||
break;
|
||||
|
||||
default:
|
||||
EA_FAIL_M("EAStdC OVprintfCore"); // This shouldn't occur unless ReadFormat started supporting some new format that we aren't yet aware of and it is being used here.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
span.mType = formatData.mModifier;
|
||||
|
||||
switch (span.mType)
|
||||
{
|
||||
case kModifierChar:
|
||||
span.mValue.mChar = (char)va_arg(arguments, int); // Recall that C++ promotes types less than int to int.
|
||||
break;
|
||||
case kModifierShort:
|
||||
span.mValue.mShort = (short)va_arg(arguments, int);
|
||||
break;
|
||||
case kModifierInt:
|
||||
span.mValue.mInt = va_arg(arguments, int);
|
||||
break;
|
||||
case kModifierLong:
|
||||
span.mValue.mLong = va_arg(arguments, long);
|
||||
break;
|
||||
case kModifierLongLong:
|
||||
span.mValue.mLongLong = va_arg(arguments, long long);
|
||||
break;
|
||||
case kModifierMax_t:
|
||||
span.mValue.mMax = va_arg(arguments, intmax_t);
|
||||
break;
|
||||
case kModifierSize_t:
|
||||
span.mValue.mSize = va_arg(arguments, size_t);
|
||||
break;
|
||||
case kModifierPtrdiff_t:
|
||||
span.mValue.mPtrDiff = va_arg(arguments, ptrdiff_t);
|
||||
break;
|
||||
case kModifierDouble:
|
||||
span.mValue.mDouble = va_arg(arguments, double);
|
||||
break;
|
||||
case kModifierLongDouble:
|
||||
span.mValue.mLongDouble = va_arg(arguments, long double);
|
||||
break;
|
||||
case kModifierWChar:
|
||||
span.mValue.mWChar = (wchar_t)va_arg(arguments, unsigned int);
|
||||
break;
|
||||
case kModifierInt8:
|
||||
span.mValue.mInt8 = (int8_t)va_arg(arguments, int);
|
||||
break;
|
||||
case kModifierInt16:
|
||||
span.mValue.mInt16 = (int16_t)va_arg(arguments, int);
|
||||
break;
|
||||
case kModifierInt32:
|
||||
span.mValue.mInt32 = va_arg(arguments, int32_t);
|
||||
break;
|
||||
case kModifierInt64:
|
||||
span.mValue.mInt64 = va_arg(arguments, int64_t);
|
||||
break;
|
||||
case kModifierInt128:
|
||||
span.mValue.mLongLong = 0;
|
||||
EA_FAIL_M("EAStdC OVprintfCore"); // We don't currently support 128 bit types in this function. We do have an int128_t class in this package which could be used, though.
|
||||
break;
|
||||
case kModifierNone:
|
||||
default:
|
||||
// If this occurs, then our ReadFormat function seems to have a bug. We already have an assertion failure for this case above.
|
||||
span.mValue.mLongLong = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have an array of spans. Now we print the spans one by one.
|
||||
for(int s = 0; s < spanIndex; ++s)
|
||||
{
|
||||
const Span& span = spans[s];
|
||||
|
||||
if(span.mpEnd != span.mpBegin) // If non-empty...
|
||||
{
|
||||
if(span.mUserIndex >= 0) // If this is a format span as opposed to a string span...
|
||||
{
|
||||
switch (span.mType)
|
||||
{
|
||||
case kModifierChar:
|
||||
// We can't call VprintfCore directly because it expects a va_list, whereas we have just a single value.
|
||||
// We can't call Sprintf because we need to pass the current pWriteFunction, pWriteFunctionContext.
|
||||
// To do: We have a problem here: VprintfCore will call the write function
|
||||
// with kWFSBegin, kWFSIntermediate, and kWFSEnd. We need it to use
|
||||
// just kFSIntermediate. Currently this affects only writing on
|
||||
// some mobile platforms to their custom log formats and it's unlikely
|
||||
// anybody will ever use ordered sprintf to such a destination.
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mChar);
|
||||
break;
|
||||
case kModifierShort:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mShort);
|
||||
break;
|
||||
case kModifierInt:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mInt);
|
||||
break;
|
||||
case kModifierLong:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mLong);
|
||||
break;
|
||||
case kModifierLongLong:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mLongLong);
|
||||
break;
|
||||
case kModifierMax_t:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mMax);
|
||||
break;
|
||||
case kModifierSize_t:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mSize);
|
||||
break;
|
||||
case kModifierPtrdiff_t:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mPtrDiff);
|
||||
break;
|
||||
case kModifierDouble:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mDouble);
|
||||
break;
|
||||
case kModifierLongDouble:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mLongDouble);
|
||||
break;
|
||||
case kModifierWChar:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mWChar);
|
||||
break;
|
||||
case kModifierInt8:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mInt8);
|
||||
break;
|
||||
case kModifierInt16:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mInt16);
|
||||
break;
|
||||
case kModifierInt32:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mInt32);
|
||||
break;
|
||||
case kModifierInt64:
|
||||
result = CallVprintfCore(pWriteFunction, pWriteFunctionContext, span.mFormat, span.mValue.mInt64);
|
||||
break;
|
||||
case kModifierInt128:
|
||||
result = -1; // We don't currently support 128 bit types in this function. We do have an int128_t class in this package which could be used, though.
|
||||
break;
|
||||
case kModifierNone:
|
||||
default:
|
||||
result = -1; // If this fails, then our ReadFormat function seems to have a bug.
|
||||
break;
|
||||
}
|
||||
|
||||
if(result < 0)
|
||||
return -1;
|
||||
|
||||
nWriteCountSum += result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We simply copy the span to the pWriteFunction, while taking care of %% escape sequences.
|
||||
p = span.mpBegin;
|
||||
pEnd = span.mpEnd;
|
||||
|
||||
if(span.mbEscapePresent) // If somewhere in the string span there is at least one %% sequence, we must copy the slow way: one by one.
|
||||
{
|
||||
for(result = 1; p < pEnd; ++p)
|
||||
{
|
||||
if(pWriteFunction(p, 1, pWriteFunctionContext, kWFSIntermediate) < 0)
|
||||
return -1;
|
||||
nWriteCountSum += result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pWriteFunction(p, (size_t)(pEnd - p), pWriteFunctionContext, kWFSIntermediate) < 0)
|
||||
return -1;
|
||||
nWriteCountSum += (int)(pEnd - p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pWriteFunction(NULL, 0, pWriteFunctionContext, kWFSEnd);
|
||||
|
||||
return nWriteCountSum;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
static int OVprintfCore(WriteFunction8 pWriteFunction8, void* EA_RESTRICT pWriteFunctionContext8, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Internal::OVprintfCore<Span8>(pWriteFunction8, pWriteFunctionContext8, pFormat, arguments);
|
||||
}
|
||||
|
||||
static int OVprintfCore(WriteFunction16 pWriteFunction16, void* EA_RESTRICT pWriteFunctionContext16, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Internal::OVprintfCore<Span16>(pWriteFunction16, pWriteFunctionContext16, pFormat, arguments);
|
||||
}
|
||||
|
||||
static int OVprintfCore(WriteFunction32 pWriteFunction32, void* EA_RESTRICT pWriteFunctionContext32, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return Internal::OVprintfCore<Span32>(pWriteFunction32, pWriteFunctionContext32, pFormat, arguments);
|
||||
}
|
||||
|
||||
} // SprintfLocal
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int OVcprintf(WriteFunction8 pWriteFunction8, void* EA_RESTRICT pContext, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(pWriteFunction8, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVfprintf(FILE* EA_RESTRICT pFile, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter8, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVprintf(const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter8, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVsprintf(char* EA_RESTRICT pDestination, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return OVsnprintf(pDestination, (size_t)-1, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVsnprintf(char* EA_RESTRICT pDestination, size_t n, const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
SprintfLocal::SnprintfContext8 sc(pDestination, 0, pDestination ? n : 0);
|
||||
|
||||
const int nRequiredLength = SprintfLocal::OVprintfCore(SprintfLocal::StringWriter8, &sc, pFormat, arguments);
|
||||
|
||||
#if EASPRINTF_SNPRINTF_C99_RETURN
|
||||
if(pDestination && (nRequiredLength >= 0))
|
||||
{
|
||||
if((size_t)nRequiredLength < n) // If there was enough space...
|
||||
pDestination[nRequiredLength] = 0;
|
||||
else if(n > 0)
|
||||
pDestination[n - 1] = 0;
|
||||
} // Else an encoding error has occurred and we can do nothing.
|
||||
|
||||
return nRequiredLength;
|
||||
#else
|
||||
if((size_t)nRequiredLength < n)
|
||||
{
|
||||
if(pDestination)
|
||||
pDestination[nRequiredLength] = 0;
|
||||
return n;
|
||||
}
|
||||
else if((n > 0) && pDestination)
|
||||
pDestination[n - 1] = 0;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int OVscprintf(const char* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
// vscprintf returns the number of chars that are needed for a printf operation.
|
||||
return OVsnprintf(NULL, 0, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OCprintf(WriteFunction8 pWriteFunction, void* EA_RESTRICT pContext, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
return SprintfLocal::OVprintfCore(pWriteFunction, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OFprintf(FILE* EA_RESTRICT pFile, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter8, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OPrintf(const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter8, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OSprintf(char* EA_RESTRICT pDestination, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return OVsnprintf(pDestination, (size_t)SprintfLocal::kNoPrecision, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OSnprintf(char* EA_RESTRICT pDestination, size_t n, const char* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return OVsnprintf(pDestination, n, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char16_t
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int OVcprintf(WriteFunction16 pWriteFunction16, void* EA_RESTRICT pContext, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(pWriteFunction16, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVfprintf(FILE* EA_RESTRICT pFile, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter16, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVprintf(const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter16, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVsprintf(char16_t* EA_RESTRICT pDestination, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return OVsnprintf(pDestination, (size_t)-1, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVsnprintf(char16_t* EA_RESTRICT pDestination, size_t n, const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
SprintfLocal::SnprintfContext16 sc(pDestination, 0, pDestination ? n : 0);
|
||||
|
||||
const int nRequiredLength = SprintfLocal::OVprintfCore(SprintfLocal::StringWriter16, &sc, pFormat, arguments);
|
||||
|
||||
#if EASPRINTF_SNPRINTF_C99_RETURN
|
||||
if(pDestination && (nRequiredLength >= 0))
|
||||
{
|
||||
if((size_t)nRequiredLength < n) // If there was enough space...
|
||||
pDestination[nRequiredLength] = 0;
|
||||
else if(n > 0)
|
||||
pDestination[n - 1] = 0;
|
||||
} // Else an encoding error has occurred and we can do nothing.
|
||||
|
||||
return nRequiredLength;
|
||||
#else
|
||||
if((size_t)nRequiredLength < n)
|
||||
{
|
||||
if(pDestination)
|
||||
pDestination[nRequiredLength] = 0;
|
||||
return n;
|
||||
}
|
||||
else if((n > 0) && pDestination)
|
||||
pDestination[n - 1] = 0;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int OVscprintf(const char16_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
// vscprintf returns the number of chars that are needed for a printf operation.
|
||||
return OVsnprintf(NULL, 0, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OCprintf(WriteFunction16 pWriteFunction, void* EA_RESTRICT pContext, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(pWriteFunction, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OFprintf(FILE* EA_RESTRICT pFile, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter16, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OPrintf(const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter16, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OSprintf(char16_t* EA_RESTRICT pDestination, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return OVsnprintf(pDestination, (size_t)SprintfLocal::kNoPrecision, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OSnprintf(char16_t* EA_RESTRICT pDestination, size_t n, const char16_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return OVsnprintf(pDestination, n, pFormat, arguments);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// char32_t
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EASTDC_API int OVcprintf(WriteFunction32 pWriteFunction32, void* EA_RESTRICT pContext, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(pWriteFunction32, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVfprintf(FILE* EA_RESTRICT pFile, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter32, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVprintf(const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter32, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVsprintf(char32_t* EA_RESTRICT pDestination, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
return OVsnprintf(pDestination, (size_t)-1, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OVsnprintf(char32_t* EA_RESTRICT pDestination, size_t n, const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
SprintfLocal::SnprintfContext32 sc(pDestination, 0, pDestination ? n : 0);
|
||||
|
||||
const int nRequiredLength = SprintfLocal::OVprintfCore(SprintfLocal::StringWriter32, &sc, pFormat, arguments);
|
||||
|
||||
#if EASPRINTF_SNPRINTF_C99_RETURN
|
||||
if(pDestination && (nRequiredLength >= 0))
|
||||
{
|
||||
if((size_t)nRequiredLength < n) // If there was enough space...
|
||||
pDestination[nRequiredLength] = 0;
|
||||
else if(n > 0)
|
||||
pDestination[n - 1] = 0;
|
||||
} // Else an encoding error has occurred and we can do nothing.
|
||||
|
||||
return nRequiredLength;
|
||||
#else
|
||||
if((size_t)nRequiredLength < n)
|
||||
{
|
||||
if(pDestination)
|
||||
pDestination[nRequiredLength] = 0;
|
||||
return n;
|
||||
}
|
||||
else if((n > 0) && pDestination)
|
||||
pDestination[n - 1] = 0;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
EASTDC_API int OVscprintf(const char32_t* EA_RESTRICT pFormat, va_list arguments)
|
||||
{
|
||||
// vscprintf returns the number of chars that are needed for a printf operation.
|
||||
return OVsnprintf(NULL, 0, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OCprintf(WriteFunction32 pWriteFunction, void* EA_RESTRICT pContext, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(pWriteFunction, pContext, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OFprintf(FILE* EA_RESTRICT pFile, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter32, pFile, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OPrintf(const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return SprintfLocal::OVprintfCore(SprintfLocal::FILEWriter32, stdout, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OSprintf(char32_t* EA_RESTRICT pDestination, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return OVsnprintf(pDestination, (size_t)SprintfLocal::kNoPrecision, pFormat, arguments);
|
||||
}
|
||||
|
||||
EASTDC_API int OSnprintf(char32_t* EA_RESTRICT pDestination, size_t n, const char32_t* EA_RESTRICT pFormat, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
va_start(arguments, pFormat);
|
||||
|
||||
return OVsnprintf(pDestination, n, pFormat, arguments);
|
||||
}
|
||||
|
||||
} // namespace StdC
|
||||
} // namespace EA
|
||||
@@ -0,0 +1,44 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/EAStdC.h>
|
||||
#include <EAStdC/internal/SprintfCore.h>
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
EASTDC_API void Init()
|
||||
{
|
||||
SprintfLocal::EASprintfInit();
|
||||
}
|
||||
|
||||
EASTDC_API void Shutdown()
|
||||
{
|
||||
SprintfLocal::EASprintfShutdown();
|
||||
}
|
||||
|
||||
|
||||
// Disabled by default, for compatibility with C99 behavior.
|
||||
bool gAssertionsEnabled = false;
|
||||
|
||||
EASTDC_API void SetAssertionsEnabled(bool enabled)
|
||||
{
|
||||
gAssertionsEnabled = enabled;
|
||||
}
|
||||
|
||||
EASTDC_API bool GetAssertionsEnabled()
|
||||
{
|
||||
return gAssertionsEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,723 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Electronic Arts Inc. All rights reserved.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Implements a stopwatch-style stopwatch. This is useful for both benchmarking
|
||||
// and for implementing runtime timing.
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#include <EAStdC/EAStopwatch.h>
|
||||
#include <EAAssert/eaassert.h>
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
#pragma warning(push, 0)
|
||||
#include <Windows.h>
|
||||
#pragma warning(pop)
|
||||
#if (defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)) || defined(EA_PLATFORM_WINDOWS_PHONE)
|
||||
EA_DISABLE_ALL_VC_WARNINGS()
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
EA_RESTORE_ALL_VC_WARNINGS()
|
||||
#endif
|
||||
#elif defined(EA_PLATFORM_SONY)
|
||||
#include <kernel.h>
|
||||
#elif defined(EA_PLATFORM_POSIX)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Auto-setup code
|
||||
//
|
||||
// The code below is for setting up the stopwatch system, and it's needed
|
||||
// because it allows us to know at runtime what the stopwatch frequency is
|
||||
// without having to spend CPU cycles repeatedly calculating it at runtime.
|
||||
// The code below is set up to auto-execute on startup when possible, but
|
||||
// it's OK if it doesn't because the Stopwatch constructor auto-checks for
|
||||
// initialization and calls if it not already. However, the auto code below
|
||||
// is still useful because it's better to get this taken care of on startup
|
||||
// rather than unexpectedly during runtime (as it takes a few milliseconds
|
||||
// on some platforms).
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#ifndef EASTDC_INIT_SEG_DEFINED
|
||||
#define EASTDC_INIT_SEG_DEFINED
|
||||
// Set initialization order between init_seg(compiler) (.CRT$XCC) and
|
||||
// init_seg(lib) (.CRT$XCL). The MSVC linker sorts the .CRT sections
|
||||
// alphabetically so we simply need to pick a name that is between
|
||||
// XCC and XCL. This works on both Windows and XBox.
|
||||
#pragma warning(disable: 4075) // "initializers put in unrecognized initialization area"
|
||||
#pragma init_seg(".CRT$XCF")
|
||||
#endif
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
// By adding the 'constructor' attribute to this function, we tell GCC
|
||||
// to have this function be called on startup before main(). We have the
|
||||
// AutoStopwatchSetup mechanism below, but GCC ignores it because the
|
||||
// object isn't externally reference and GCC thinks it can thus be eliminated.
|
||||
void EAStdCStopwatchSetupCoefficients();
|
||||
void EAStdCStopwatchSetup() __attribute__ ((constructor));
|
||||
void EAStdCStopwatchDisableCPUCalibration(uint64_t cpuFrequency);
|
||||
|
||||
#else
|
||||
// Some compilers require these functions to be pre-declared.
|
||||
void EAStdCStopwatchSetupCoefficients();
|
||||
void EAStdCStopwatchSetup();
|
||||
void EAStdCStopwatchDisableCPUCalibration(uint64_t cpuFrequency);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Anonymous namespace
|
||||
// Has the effect of making things declared within it be static, but with some improvements.
|
||||
namespace
|
||||
{
|
||||
// Stopwatch cycle metrics
|
||||
uint64_t mnStopwatchFrequency(1); // Set to one to prevent possible div/0 errors.
|
||||
float mfStopwatchCyclesToNanosecondsCoefficient;
|
||||
float mfStopwatchCyclesToMicrosecondsCoefficient;
|
||||
float mfStopwatchCyclesToMillisecondsCoefficient;
|
||||
float mfStopwatchCyclesToSecondsCoefficient;
|
||||
float mfStopwatchCyclesToMinutesCoefficient;
|
||||
|
||||
// CPU cycle metrics
|
||||
uint64_t mnCPUFrequency(1); // Set to one to prevent possible div/0 errors.
|
||||
float mfCPUCyclesToNanosecondsCoefficient;
|
||||
float mfCPUCyclesToMicrosecondsCoefficient;
|
||||
float mfCPUCyclesToMillisecondsCoefficient;
|
||||
float mfCPUCyclesToSecondsCoefficient;
|
||||
float mfCPUCyclesToMinutesCoefficient;
|
||||
|
||||
#if EASTDC_STOPWATCH_OVERHEAD_ENABLED
|
||||
uint64_t mnCPUCycleReadingOverhead(0);
|
||||
uint64_t mnStopwatchCycleReadingOverhead(0);
|
||||
#endif
|
||||
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
void EAStdCThreadSleep(int ms)
|
||||
{
|
||||
#if (defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)) || defined(EA_PLATFORM_WINDOWS_PHONE)
|
||||
std::chrono::milliseconds duration(ms);
|
||||
std::this_thread::sleep_for(duration);
|
||||
#else
|
||||
::SleepEx((DWORD)ms, TRUE);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
void EAStdCStopwatchSetupCoefficients()
|
||||
{
|
||||
// Calculate coefficients.
|
||||
mfStopwatchCyclesToMinutesCoefficient = 1.f / 60.f / (int64_t)mnStopwatchFrequency; // Some compilers require the
|
||||
mfStopwatchCyclesToSecondsCoefficient = 1.f / (int64_t)mnStopwatchFrequency; // conversion to int64_t for the math.
|
||||
mfStopwatchCyclesToMillisecondsCoefficient = 1000.f / (int64_t)mnStopwatchFrequency;
|
||||
mfStopwatchCyclesToMicrosecondsCoefficient = 1000000.f / (int64_t)mnStopwatchFrequency;
|
||||
mfStopwatchCyclesToNanosecondsCoefficient = 1000000000.f / (int64_t)mnStopwatchFrequency;
|
||||
|
||||
#if EASTDC_STOPWATCH_OVERHEAD_ENABLED
|
||||
// Here we make a rough measurement of the start and stop overhead of
|
||||
// the stopwatch code. It is hard to say what a good way to determine this is,
|
||||
// as the runtime use of the stopwatch will actually cause the overhead to
|
||||
// vary somewhat between uses. It is perhaps even debateable whether we
|
||||
// should even be attempting to do such overhead calculations.
|
||||
uint64_t nCurrentStopwatchCycleValue1;
|
||||
uint64_t nCurrentStopwatchCycleValue2;
|
||||
uint64_t nLowestStopwatchCycleReadingOverhead(UINT64_MAX);
|
||||
uint64_t nCurrentStopwatchCycleReadingOverhead;
|
||||
|
||||
for(int t(0); t < 8; t++)
|
||||
{
|
||||
nCurrentStopwatchCycleValue1 = EA::StdC::Stopwatch::GetStopwatchCycle();
|
||||
nCurrentStopwatchCycleValue2 = EA::StdC::Stopwatch::GetStopwatchCycle();
|
||||
nCurrentStopwatchCycleReadingOverhead = nCurrentStopwatchCycleValue2 - nCurrentStopwatchCycleValue1;
|
||||
if(nLowestStopwatchCycleReadingOverhead > nCurrentStopwatchCycleReadingOverhead)
|
||||
nLowestStopwatchCycleReadingOverhead = nCurrentStopwatchCycleReadingOverhead;
|
||||
}
|
||||
mnStopwatchCycleReadingOverhead = nLowestStopwatchCycleReadingOverhead;
|
||||
#endif
|
||||
|
||||
// CPU Frequencies
|
||||
mfCPUCyclesToMinutesCoefficient = 1.f / 60.f / (int64_t)mnCPUFrequency;
|
||||
mfCPUCyclesToSecondsCoefficient = 1.f / (int64_t)mnCPUFrequency;
|
||||
mfCPUCyclesToMillisecondsCoefficient = 1000.f / (int64_t)mnCPUFrequency;
|
||||
mfCPUCyclesToMicrosecondsCoefficient = 1000000.f / (int64_t)mnCPUFrequency;
|
||||
mfCPUCyclesToNanosecondsCoefficient = 1000000000.f / (int64_t)mnCPUFrequency;
|
||||
|
||||
#if EASTDC_STOPWATCH_OVERHEAD_ENABLED
|
||||
uint64_t nCurrentCPUCycleValue1, nCurrentCPUCycleValue2;
|
||||
uint64_t nLowestCPUCycleReadingOverhead(UINT64_MAX);
|
||||
uint64_t nCurrentCPUCycleReadingOverhead;
|
||||
|
||||
for(int c = 0; c < 8; c++)
|
||||
{
|
||||
nCurrentCPUCycleValue1 = EA::StdC::Stopwatch::GetCPUCycle();
|
||||
nCurrentCPUCycleValue2 = EA::StdC::Stopwatch::GetCPUCycle();
|
||||
nCurrentCPUCycleReadingOverhead = nCurrentCPUCycleValue2 - nCurrentCPUCycleValue1;
|
||||
|
||||
if(nLowestCPUCycleReadingOverhead > nCurrentCPUCycleReadingOverhead)
|
||||
nLowestCPUCycleReadingOverhead = nCurrentCPUCycleReadingOverhead;
|
||||
}
|
||||
|
||||
mnCPUCycleReadingOverhead = nLowestCPUCycleReadingOverhead;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void EAStdCStopwatchSetup()
|
||||
{
|
||||
if(mnStopwatchFrequency <= 1) // If we haven't already calculated this...
|
||||
{
|
||||
#if defined(EA_PLATFORM_SONY)
|
||||
// According to Sony: A time stamp counter exists for each CPU core, but the frequency is the same
|
||||
// value for all CPU cores. This frequency will not change during the lifetime of a process.
|
||||
mnCPUFrequency = sceKernelGetProcessTimeCounterFrequency();
|
||||
mnStopwatchFrequency = mnCPUFrequency;
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
mach_timebase_info_data_t timebaseInfo;
|
||||
|
||||
mach_timebase_info(&timebaseInfo);
|
||||
mnCPUFrequency = (UINT64_C(1000000000) * (uint64_t)timebaseInfo.denom) / (uint64_t)timebaseInfo.numer;
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Stopwatch Frequency
|
||||
mnStopwatchFrequency = mnCPUFrequency;
|
||||
|
||||
#elif defined(EA_PLATFORM_XBOXONE) || defined(CS_UNDEFINED_STRING)
|
||||
// Microsoft has stated (https://forums.xboxlive.com/AnswerPage.aspx?qid=c3fcdad5-f3e4-46d9-85f9-d337506f0d6b&tgt=1) that
|
||||
// QueryPerformanceCounter / QueryPerformanceFrequency map to rdtsc and they are stable throughout the life of the process.
|
||||
// Thus we can use QueryPerformanceFrequency to portable tell the CPU frequency for our usage of rdtsc.
|
||||
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&mnStopwatchFrequency));
|
||||
mnCPUFrequency = mnStopwatchFrequency;
|
||||
|
||||
#elif defined(EA_PLATFORM_MICROSOFT)
|
||||
// On Windows, the only way to tell the CPU-based timer frequency is to manually
|
||||
// time it. There is no function in Windows which tells you this information.
|
||||
// Indeed such a function might not be useful for CPU frequency-switching systems.
|
||||
|
||||
// SetPriorityClass / SetThreadPriority APIs not available for Windows 8 Metro apps
|
||||
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
|
||||
HANDLE process = ::GetCurrentProcess();
|
||||
DWORD oldPriorityClass = ::GetPriorityClass(process);
|
||||
HANDLE thread = ::GetCurrentThread();
|
||||
int oldThreadPriority = ::GetThreadPriority(thread);
|
||||
|
||||
// Set process/thread priorities.
|
||||
::SetPriorityClass(process, REALTIME_PRIORITY_CLASS);
|
||||
::SetThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL);
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Stopwatch Frequency
|
||||
////////////////////////////////////////////////////
|
||||
// CPU Frequency
|
||||
// Unfortunately, you can't call Win32 QueryPerformanceFrequency and
|
||||
// expect that it will give you the processor frequency in megahertz.
|
||||
// Doing such a thing would work for some CPUs and not others. In fact,
|
||||
// under Windows 2000, it works for my PIII-733 but not for my PII-300.
|
||||
// So what we do instead is simply find the ratio of the QueryPerformanceCounter
|
||||
// and our Stopwatch::GetCPUCycle functions.
|
||||
mnCPUFrequency = 0;
|
||||
for(int i(0); i < 5; i++) // Give N chances at this, in case OS pre-emption occurs.
|
||||
{
|
||||
uint64_t nQPCCounter; ::QueryPerformanceCounter((LARGE_INTEGER*)&nQPCCounter);
|
||||
uint64_t nCPUCounter = EA::StdC::Stopwatch::GetCPUCycle();
|
||||
|
||||
double dRatio = (double)nCPUCounter / (double)nQPCCounter;
|
||||
|
||||
if((dRatio > 0.98 && dRatio < 1.02))
|
||||
{
|
||||
::QueryPerformanceFrequency((LARGE_INTEGER*)&mnCPUFrequency);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mnCPUFrequency)
|
||||
{
|
||||
// Do our own manual timing of clock ticks.
|
||||
// We use Win32 QueryPerformanceCounter and QueryPerformanceFrequency to
|
||||
// calibrate our CPU cycle counter.
|
||||
uint64_t nQPFrequency, nQPCCounter1, nQPCCounter2;
|
||||
uint64_t nCPUCounter1, nCPUCounter2=0;
|
||||
double dQPCSeconds(0);
|
||||
|
||||
::QueryPerformanceFrequency((LARGE_INTEGER*)&nQPFrequency);
|
||||
::QueryPerformanceCounter((LARGE_INTEGER*)&nQPCCounter1);
|
||||
nCPUCounter1 = EA::StdC::Stopwatch::GetCPUCycle();
|
||||
|
||||
#if EASTDC_STOPWATCH_FORCE_CPU_CYCLE_USAGE
|
||||
static const double TIME_IN_SECONDS_TO_MEASURE_TICKS = 0.300;
|
||||
#else
|
||||
// Given that ticks are considered to be unreliable we can tolerate lower accuracy measurement
|
||||
// of number of ticks per second. https://en.wikipedia.org/wiki/Time_Stamp_Counter
|
||||
// Here the largest limitation will be the MS Perf counter accuracy which is less than 1us.
|
||||
static const double TIME_IN_SECONDS_TO_MEASURE_TICKS = 0.005;
|
||||
#endif
|
||||
|
||||
while(dQPCSeconds < TIME_IN_SECONDS_TO_MEASURE_TICKS)
|
||||
{
|
||||
::QueryPerformanceCounter((LARGE_INTEGER*)&nQPCCounter2);
|
||||
nCPUCounter2 = EA::StdC::Stopwatch::GetCPUCycle();
|
||||
dQPCSeconds = (nQPCCounter2 - nQPCCounter1) / (double)nQPFrequency;
|
||||
}
|
||||
|
||||
mnCPUFrequency = (uint64_t)((nCPUCounter2 - nCPUCounter1) / dQPCSeconds);
|
||||
}
|
||||
|
||||
#if EASTDC_STOPWATCH_FORCE_CPU_CYCLE_USAGE
|
||||
mnStopwatchFrequency = mnCPUFrequency;
|
||||
#else
|
||||
::QueryPerformanceFrequency((LARGE_INTEGER*)&mnStopwatchFrequency);
|
||||
#endif
|
||||
|
||||
// Reset process/thread priorities.
|
||||
#if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
|
||||
::SetPriorityClass(process, oldPriorityClass);
|
||||
::SetThreadPriority(thread, oldThreadPriority);
|
||||
#endif
|
||||
#else
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// CPU Frequency
|
||||
#ifdef EASTDC_CPU_FREQ_CALCULATED
|
||||
// On Unix, the only way to tell the CPU-based timer frequency is to manually
|
||||
// time it. There is no function in Unix which tells you this information.
|
||||
// Indeed such a function might not be useful for CPU frequency-switching systems.
|
||||
|
||||
uint64_t nTimeCounter1 = EA::StdC::Stopwatch::GetStopwatchCycle();
|
||||
uint64_t nCPUCounter1 = EA::StdC::Stopwatch::GetCPUCycle();
|
||||
|
||||
usleep(250000); // Sleep for a ~quarter second.
|
||||
|
||||
uint64_t nCPUCounter2 = EA::StdC::Stopwatch::GetCPUCycle();
|
||||
uint64_t nTimeCounter2 = EA::StdC::Stopwatch::GetStopwatchCycle();
|
||||
uint64_t nTimeDeltaUs = nTimeCounter2 - nTimeCounter1;
|
||||
uint64_t nCPUDeltaTicks = nCPUCounter2 - nCPUCounter1;
|
||||
|
||||
// GetStopwatchCycle will have varying resolution so we need to account for that accordingly
|
||||
#if EASTDC_STOPWATCH_USE_CLOCK_GETTIME
|
||||
mnCPUFrequency = (nCPUDeltaTicks * 1000000000 / nTimeDeltaUs); // We are using clock_gettime, which works in nanoseconds.
|
||||
#elif EASTDC_STOPWATCH_USE_GETTIMEOFDAY
|
||||
mnCPUFrequency = (nCPUDeltaTicks * 1000000 / nTimeDeltaUs); // We are using gettimeofday, which works in microseconds.
|
||||
#else
|
||||
mnCPUFrequency = (nCPUDeltaTicks * 1000000 / nTimeDeltaUs);
|
||||
#endif
|
||||
|
||||
#if EASTDC_STOPWATCH_OVERHEAD_ENABLED
|
||||
// To do. Slightly less accuracy without this implemented.
|
||||
#endif
|
||||
|
||||
#elif EASTDC_STOPWATCH_USE_CLOCK_GETTIME
|
||||
mnCPUFrequency = UINT64_C(1000000000); // We are using clock_gettime, which works in nanoseconds.
|
||||
|
||||
#elif EASTDC_STOPWATCH_USE_GETTIMEOFDAY
|
||||
mnCPUFrequency = 1000000; // We are using gettimeofday, which works in microseconds.
|
||||
#else
|
||||
#error
|
||||
mnCPUFrequency = 1;
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Stopwatch Frequency
|
||||
#if EASTDC_STOPWATCH_USE_CLOCK_GETTIME
|
||||
mnStopwatchFrequency = UINT64_C(1000000000); // We are using clock_gettime, which works in nanoseconds.
|
||||
#elif EASTDC_STOPWATCH_USE_GETTIMEOFDAY
|
||||
mnStopwatchFrequency = 1000000; // We are using gettimeofday, which works in microseconds.
|
||||
#else
|
||||
mnStopwatchFrequency = mnCPUFrequency;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
EAStdCStopwatchSetupCoefficients();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// EAStdCStopwatchDisableCPUCalibration
|
||||
//
|
||||
// This function circumvents the EAStdCStopwatchSetup function for the purpose
|
||||
// of making it so that EAStdCStopwatchSetup doesn't take a long time to execute
|
||||
// on startup. If this function is executed before EAStdCStopwatchSetup then
|
||||
// if EAStdCStopwatchSetup is later executed then it will just immediately exit.
|
||||
// This function can be considered an alternative to the EAStdCStopwatchSetup
|
||||
// function that executes much faster. The downside is that the CPU frequency
|
||||
// will not be calculated or known and thus the CPU-based timing functions won't
|
||||
// be available (though the system time-based timing functions will be). If you
|
||||
// have a utility app that you need to run which needs to execute quickly and
|
||||
// doesn't need CPU-based clock tick timing precision, you might want to use
|
||||
// this function. See the EASTDC_STOPWATCH_DISABLE_CPU_CALIBRATION function for
|
||||
// how to do this. The most important thing is that this function needs to be
|
||||
// called before the EAStdCStopwatchSetup function, which may require some
|
||||
// compiler/platform-specific trickery as documented with EASTDC_STOPWATCH_DISABLE_CPU_CALIBRATION.
|
||||
//
|
||||
EASTDC_API void EAStdCStopwatchDisableCPUCalibration(uint64_t cpuFrequency)
|
||||
{
|
||||
if(cpuFrequency)
|
||||
mnCPUFrequency = cpuFrequency;
|
||||
else
|
||||
mnCPUFrequency = UINT64_C(2000000000); // We use a moderate guess here of 2GHz. To consider: Make this a very inaccurate value so that it's obvious it's wrong at runtime.
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Stopwatch Frequency
|
||||
#if EASTDC_STOPWATCH_FORCE_CPU_CYCLE_USAGE
|
||||
mnStopwatchFrequency = mnCPUFrequency;
|
||||
#else
|
||||
#if defined(EA_PLATFORM_MICROSOFT)
|
||||
::QueryPerformanceFrequency((LARGE_INTEGER*)&mnStopwatchFrequency);
|
||||
#else
|
||||
// To do.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
EAStdCStopwatchSetupCoefficients();
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace EA
|
||||
{
|
||||
namespace StdC
|
||||
{
|
||||
//We have this #if !defined() commented out because we aren't sure that all GCC versions act the same across all platforms.
|
||||
//#if !defined(__GNUC__) // GCC uses __attribute__((constructor)) instead of this to call EAStdCStopwatchSetup.
|
||||
// AutoStopwatchSetup
|
||||
// We create this static class in order to trigger
|
||||
// automatic calling of the code below.
|
||||
struct AutoStopwatchSetup
|
||||
{
|
||||
AutoStopwatchSetup()
|
||||
{ EAStdCStopwatchSetup(); }
|
||||
};
|
||||
|
||||
AutoStopwatchSetup gAutoStopwatchSetup EA_INIT_PRIORITY(1000);
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Stopwatch
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EA::StdC::Stopwatch::Stopwatch(int nUnits, bool bStartImmediately)
|
||||
: mnStartTime(0),
|
||||
mnTotalElapsedTime(0),
|
||||
mnUnits(0),
|
||||
mfStopwatchCyclesToUnitsCoefficient(1.f)
|
||||
{
|
||||
SetUnits(nUnits);
|
||||
if(mnStopwatchFrequency <= 1) // If not already initialized...
|
||||
EAStdCStopwatchSetup();
|
||||
if(bStartImmediately)
|
||||
Start();
|
||||
}
|
||||
|
||||
|
||||
void EA::StdC::Stopwatch::SetUnits(int nUnits)
|
||||
{
|
||||
mnUnits = nUnits;
|
||||
|
||||
mfStopwatchCyclesToUnitsCoefficient = 1;
|
||||
|
||||
switch (mnUnits)
|
||||
{
|
||||
case kUnitsCPUCycles:
|
||||
mfStopwatchCyclesToUnitsCoefficient = 1; // Timing is reported directly with GetCPUCycle().
|
||||
break;
|
||||
|
||||
case kUnitsCycles:
|
||||
mfStopwatchCyclesToUnitsCoefficient = 1; // Timing is reported directly with GetStopwatchCycle().
|
||||
break;
|
||||
|
||||
case kUnitsNanoseconds:
|
||||
mfStopwatchCyclesToUnitsCoefficient = mfStopwatchCyclesToNanosecondsCoefficient;
|
||||
break;
|
||||
|
||||
case kUnitsMicroseconds:
|
||||
mfStopwatchCyclesToUnitsCoefficient = mfStopwatchCyclesToMicrosecondsCoefficient;
|
||||
break;
|
||||
|
||||
case kUnitsMilliseconds:
|
||||
mfStopwatchCyclesToUnitsCoefficient = mfStopwatchCyclesToMillisecondsCoefficient;
|
||||
break;
|
||||
|
||||
case kUnitsSeconds:
|
||||
mfStopwatchCyclesToUnitsCoefficient = mfStopwatchCyclesToSecondsCoefficient;
|
||||
break;
|
||||
|
||||
case kUnitsMinutes:
|
||||
mfStopwatchCyclesToUnitsCoefficient = mfStopwatchCyclesToMinutesCoefficient;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EA::StdC::Stopwatch::Stop()
|
||||
{
|
||||
if(mnStartTime) // Check to make sure the stopwatch is actually running
|
||||
{
|
||||
// Note that below we compare the elapsed time (from the most recent start
|
||||
// to the this stop) to sStopwatchCycleReadingOverhead. For most timing situations,
|
||||
// the elapsed time will be *much* greater than the overhead. For some
|
||||
// cases (e.g. timing 10-20 lines of C code) the elapsed time will be only
|
||||
// 3-10 times the value of sStopwatchCycleReadingOverhead. In these cases, the
|
||||
// value of subtracting this overhead may be useful. For some cases the
|
||||
// code being timed is so small or brief that sStopwatchCycleReadingOverhead may
|
||||
// actually come out to be higher than the stretch of code. If this is the
|
||||
// case, you really don't want to be trying to time this code with a
|
||||
// softare-based stopwatch. What we do is simply set the elapsed time to a
|
||||
// small value such as 1, in order for code that is using this stopwatch to at
|
||||
// least believe that something is happening.
|
||||
|
||||
const uint64_t nCurrentTime(mnUnits == kUnitsCPUCycles ? GetCPUCycle() : GetStopwatchCycle());
|
||||
|
||||
// EA_ASSERT(nCurrentTime >= mnStartTime); We might want to enable this, at least for modern platforms.
|
||||
|
||||
#if EASTDC_STOPWATCH_OVERHEAD_ENABLED
|
||||
const uint64_t nElapsedTime(nCurrentTime - mnStartTime);
|
||||
|
||||
if(nElapsedTime > mnStopwatchCycleReadingOverhead)
|
||||
mnTotalElapsedTime += nElapsedTime - mnStopwatchCycleReadingOverhead;
|
||||
else
|
||||
mnTotalElapsedTime += 1; // We pretend that just one stopwatch cycle has elapsed.
|
||||
#else
|
||||
mnTotalElapsedTime += (nCurrentTime - mnStartTime);
|
||||
#endif
|
||||
|
||||
mnStartTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint64_t EA::StdC::Stopwatch::GetElapsedTime() const
|
||||
{
|
||||
uint64_t nFinalTotalElapsedTime64(mnTotalElapsedTime);
|
||||
|
||||
if(mnStartTime) // We we are currently running, then take into account time passed since last start.
|
||||
{
|
||||
|
||||
uint64_t nCurrentTime;
|
||||
|
||||
// See the 'Stop' function for an explanation of the code below.
|
||||
if(mnUnits == kUnitsCPUCycles)
|
||||
nCurrentTime = GetCPUCycle();
|
||||
else
|
||||
nCurrentTime = GetStopwatchCycle();
|
||||
|
||||
uint64_t nElapsed = nCurrentTime - mnStartTime;
|
||||
|
||||
// EA_ASSERT(nCurrentTime >= mnStartTime); We might want to enable this, at least for modern platforms.
|
||||
|
||||
#if EASTDC_STOPWATCH_OVERHEAD_ENABLED
|
||||
const uint64_t nElapsedTime = nElapsed;
|
||||
|
||||
if(nElapsedTime > mnStopwatchCycleReadingOverhead)
|
||||
nFinalTotalElapsedTime64 += nElapsedTime - mnStopwatchCycleReadingOverhead;
|
||||
else
|
||||
nFinalTotalElapsedTime64 += 1; // We pretend that just one stopwatch cycle has elapsed.
|
||||
#else
|
||||
nFinalTotalElapsedTime64 += nElapsed;
|
||||
#endif
|
||||
|
||||
} // Now nFinalTotalElapsedTime64 holds the elapsed time in stopwatch cycles.
|
||||
|
||||
if(mfStopwatchCyclesToUnitsCoefficient == 0) // If the stopwatch was started before the global EAStdCStopwatchSetup function was executed...
|
||||
{
|
||||
// We can recover from this by calling SetUnits here, which will re-read the global setup results.
|
||||
const_cast<Stopwatch*>(this)->SetUnits(mnUnits);
|
||||
// EA_ASSERT(mfStopwatchCyclesToUnitsCoefficient != 0);
|
||||
}
|
||||
|
||||
// We are doing a float to int cast here, which is a relatively slow operation on some CPUs.
|
||||
return (uint64_t)((nFinalTotalElapsedTime64 * mfStopwatchCyclesToUnitsCoefficient) + 0.49999f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void EA::StdC::Stopwatch::SetElapsedTime(uint64_t nElapsedTime)
|
||||
{
|
||||
if(IsRunning())
|
||||
Restart();
|
||||
|
||||
// Concern: The division here is not lightning fast and also is subject to precision error.
|
||||
mnTotalElapsedTime = (uint64_t)((nElapsedTime / mfStopwatchCyclesToUnitsCoefficient) + 0.49999f);
|
||||
}
|
||||
|
||||
|
||||
float EA::StdC::Stopwatch::GetElapsedTimeFloat() const
|
||||
{
|
||||
uint64_t nFinalTotalElapsedTime64(mnTotalElapsedTime);
|
||||
|
||||
if(mnStartTime){ //We we are currently running, then take into account time passed since last start.
|
||||
|
||||
uint64_t nCurrentTime;
|
||||
|
||||
// See the 'Stop' function for an explanation of the code below.
|
||||
if(mnUnits == kUnitsCPUCycles)
|
||||
nCurrentTime = GetCPUCycle();
|
||||
else
|
||||
nCurrentTime = GetStopwatchCycle();
|
||||
|
||||
uint64_t nElapsed = nCurrentTime - mnStartTime;
|
||||
|
||||
// EA_ASSERT(nCurrentTime >= mnStartTime); We might want to enable this, at least for modern platforms.
|
||||
|
||||
#if EASTDC_STOPWATCH_OVERHEAD_ENABLED
|
||||
const uint64_t nElapsedTime = nElapsed;
|
||||
|
||||
if(nElapsedTime > mnStopwatchCycleReadingOverhead)
|
||||
nFinalTotalElapsedTime64 += nElapsedTime - mnStopwatchCycleReadingOverhead;
|
||||
else
|
||||
nFinalTotalElapsedTime64 += 1; // We pretend that just one stopwatch cycle has elapsed.
|
||||
#else
|
||||
nFinalTotalElapsedTime64 += nElapsed;
|
||||
#endif
|
||||
|
||||
} // Now nFinalTotalElapsedTime64 holds the elapsed time in stopwatch cycles.
|
||||
|
||||
// We are doing a float to int cast here, which is a relatively slow operation on some CPUs.
|
||||
return (float)(nFinalTotalElapsedTime64 * mfStopwatchCyclesToUnitsCoefficient);
|
||||
}
|
||||
|
||||
|
||||
void EA::StdC::Stopwatch::SetElapsedTimeFloat(float fElapsedTime)
|
||||
{
|
||||
if(IsRunning())
|
||||
Restart();
|
||||
|
||||
// Concern: The division here is not lightning fast and also is subject to precision error.
|
||||
mnTotalElapsedTime = (uint64_t)(fElapsedTime / mfStopwatchCyclesToUnitsCoefficient);
|
||||
}
|
||||
|
||||
|
||||
float EA::StdC::Stopwatch::GetUnitsPerStopwatchCycle(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case kUnitsNanoseconds:
|
||||
return mfStopwatchCyclesToNanosecondsCoefficient;
|
||||
|
||||
case kUnitsMicroseconds:
|
||||
return mfStopwatchCyclesToMicrosecondsCoefficient;
|
||||
|
||||
case kUnitsMilliseconds:
|
||||
return mfStopwatchCyclesToMillisecondsCoefficient;
|
||||
|
||||
case kUnitsSeconds:
|
||||
return mfStopwatchCyclesToSecondsCoefficient;
|
||||
|
||||
case kUnitsMinutes:
|
||||
return mfStopwatchCyclesToMinutesCoefficient;
|
||||
|
||||
case kUnitsCycles:
|
||||
case kUnitsCPUCycles:
|
||||
case kUnitsUserDefined:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
float EA::StdC::Stopwatch::GetUnitsPerCPUCycle(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case kUnitsNanoseconds:
|
||||
return mfCPUCyclesToNanosecondsCoefficient;
|
||||
|
||||
case kUnitsMicroseconds:
|
||||
return mfCPUCyclesToMicrosecondsCoefficient;
|
||||
|
||||
case kUnitsMilliseconds:
|
||||
return mfCPUCyclesToMillisecondsCoefficient;
|
||||
|
||||
case kUnitsSeconds:
|
||||
return mfCPUCyclesToSecondsCoefficient;
|
||||
|
||||
case kUnitsMinutes:
|
||||
return mfCPUCyclesToMinutesCoefficient;
|
||||
|
||||
case kUnitsCycles:
|
||||
case kUnitsCPUCycles:
|
||||
case kUnitsUserDefined:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// This function is here instead of inlined in the associated header file
|
||||
// because it uses mnStopwatchFrequency, which is a variable local to this
|
||||
// source file.
|
||||
uint64_t EA::StdC::Stopwatch::GetStopwatchFrequency()
|
||||
{
|
||||
return mnStopwatchFrequency;
|
||||
}
|
||||
|
||||
|
||||
// This function is here instead of inlined in the associated header file
|
||||
// because it uses mnStopwatchFrequency, which is a variable local to this
|
||||
// source file.
|
||||
uint64_t EA::StdC::Stopwatch::GetCPUFrequency()
|
||||
{
|
||||
return mnCPUFrequency;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// LimitStopwatch
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EA::StdC::LimitStopwatch::SetTimeLimit(uint64_t nLimit, bool bStartImmediately)
|
||||
{
|
||||
const uint64_t nCurrentTime = GetStopwatchCycle();
|
||||
|
||||
mnEndTime = nCurrentTime + (uint64_t)(nLimit / mfStopwatchCyclesToUnitsCoefficient);
|
||||
|
||||
if(bStartImmediately)
|
||||
Start();
|
||||
}
|
||||
|
||||
|
||||
float EA::StdC::LimitStopwatch::GetTimeRemainingFloat() const
|
||||
{
|
||||
const uint64_t nCurrentTime = GetStopwatchCycle();
|
||||
|
||||
const float fTimeRemaining = (float)((int64_t)(mnEndTime - nCurrentTime)) * mfStopwatchCyclesToUnitsCoefficient;
|
||||
|
||||
return fTimeRemaining;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user