diff options
Diffstat (limited to 'Runtime/Utilities')
88 files changed, 15299 insertions, 0 deletions
diff --git a/Runtime/Utilities/Annotations.h b/Runtime/Utilities/Annotations.h new file mode 100644 index 0000000..39f7e5a --- /dev/null +++ b/Runtime/Utilities/Annotations.h @@ -0,0 +1,52 @@ +#ifndef ANNOTATIONS_H +#define ANNOTATIONS_H + +// Annotations for various stuff. +// At the moment only understood by Visual Studio, and expand to nothing elsewhere. + +#if UNITY_WIN +#pragma warning(disable:6255) // _alloca +#pragma warning(disable:6211) // leaking due to exception +#define INPUT_NOTNULL _In_ +#define INPUT_OPTIONAL _In_opt_ +#define OUTPUT_NOTNULL _Out_ +#define OUTPUT_OPTIONAL _Out_opt_ +#define INOUT_NOTNULL _Inout_ +#define INOUT_OPTIONAL _Inout_opt_ +#define RETVAL_NOTNULL _Ret_ +#define RETVAL_OPTIONAL _Ret_opt_ +#define DOES_NOT_RETURN __declspec(noreturn) +#define ANALYSIS_ASSUME(x) { __analysis_assume(x); } +#define TAKES_PRINTF_ARGS(n,m) + +#elif defined(__GNUC__) + +#define INPUT_NOTNULL +#define INPUT_OPTIONAL +#define OUTPUT_NOTNULL +#define OUTPUT_OPTIONAL +#define INOUT_NOTNULL +#define INOUT_OPTIONAL +#define RETVAL_NOTNULL +#define RETVAL_OPTIONAL +#define DOES_NOT_RETURN __attribute__((noreturn)) +#define ANALYSIS_ASSUME(x) +#define TAKES_PRINTF_ARGS(m,n) __attribute__((format(printf,m,n))) + +#else + +#define INPUT_NOTNULL +#define INPUT_OPTIONAL +#define OUTPUT_NOTNULL +#define OUTPUT_OPTIONAL +#define INOUT_NOTNULL +#define INOUT_OPTIONAL +#define RETVAL_NOTNULL +#define RETVAL_OPTIONAL +#define DOES_NOT_RETURN +#define ANALYSIS_ASSUME(x) +#define TAKES_PRINTF_ARGS(n,m) + +#endif + +#endif diff --git a/Runtime/Utilities/Argv.cpp b/Runtime/Utilities/Argv.cpp new file mode 100644 index 0000000..b05da23 --- /dev/null +++ b/Runtime/Utilities/Argv.cpp @@ -0,0 +1,129 @@ +#include "UnityPrefix.h" +#include "Argv.h" +#if UNITY_WIN +#include "PlatformDependent/Win/WinUtils.h" +#endif + +using namespace std; + +static int argc; +static const char** argv; +static vector<string> relaunchArguments; + +struct KnownArguments +{ + bool isBatchmode; + bool isAutomated; +}; + +static KnownArguments knownArgs; + +void SetupArgv (int a, const char** b) +{ + argc = a; + argv = b; + knownArgs.isBatchmode = HasARGV ("batchmode"); + knownArgs.isAutomated = HasARGV ("automated"); +} + +bool HasARGV (const string& name) +{ + for (int i=0;i<argc;i++) + { + if (StrICmp (argv[i], "-" + name) == 0) + return true; + } + return false; +} + +bool IsBatchmode () +{ + return knownArgs.isBatchmode; +} + +bool IsHumanControllingUs () +{ + return !(knownArgs.isBatchmode || knownArgs.isAutomated); +} + +void SetIsBatchmode (bool value) +{ + knownArgs.isBatchmode = true; +} + +void PrintARGV () +{ + for (int i=0;i<argc;i++) + { + printf_console ("%s\n", argv[i]); + } +} + +vector<string> GetValuesForARGV (const string& name) +{ + vector<string> values; + values.reserve (argc); + + bool found = false; + for (int i=0;i<argc;i++) + { + if (found) + { + if (argv[i][0] == '-') + return values; + else + values.push_back (argv[i]); + } + else if (StrICmp (argv[i], "-" + name) == 0) + found = true; + } + + return values; +} + +string GetFirstValueForARGV (const string& name) +{ + vector<string> values = GetValuesForARGV (name); + if (values.empty ()) + return string (); + else + return values[0]; +} + +vector<string> GetAllArguments() +{ + vector<string> values; + values.reserve (argc); + for (int i=1;i<argc;i++) + values.push_back (argv[i]); + return values; +} + +void SetRelaunchApplicationArguments (const vector<string>& args) +{ + relaunchArguments = args; +} + +vector<string> GetRelaunchApplicationArguments () +{ + return relaunchArguments; +} + +void CheckBatchModeErrorString (const string& error) +{ + if (error.empty ()) + return; + + ErrorString(error); + + if (!IsBatchmode()) + return; + +#if UNITY_WIN && UNITY_EDITOR + winutils::RedirectStdoutToConsole(); +#elif UNITY_OSX + ResetStdout(); +#endif + printf_console ("\nAborting batchmode due to failure:\n%s\n\n", error.c_str()); + exit(1); +} diff --git a/Runtime/Utilities/Argv.h b/Runtime/Utilities/Argv.h new file mode 100644 index 0000000..835b4bc --- /dev/null +++ b/Runtime/Utilities/Argv.h @@ -0,0 +1,30 @@ +#ifndef ARGV_H +#define ARGV_H + +void SetupArgv (int argc, const char** argv); + +const char **GetArgv(); +int GetArgc(); + +/// Returns true if the commandline contains a -name. +bool HasARGV (const std::string& name); + +bool IsBatchmode (); +bool IsHumanControllingUs (); + +void SetIsBatchmode (bool value); + +/// Returns a list of values for the argument +std::vector<std::string> GetValuesForARGV (const std::string& name); + +std::vector<std::string> GetAllArguments(); +std::string GetFirstValueForARGV (const std::string& name); + +void SetRelaunchApplicationArguments (const std::vector<std::string>& args); +std::vector<std::string> GetRelaunchApplicationArguments (); + +void PrintARGV (); + +void CheckBatchModeErrorString (const std::string& error); + +#endif diff --git a/Runtime/Utilities/ArrayUtility.h b/Runtime/Utilities/ArrayUtility.h new file mode 100644 index 0000000..c618296 --- /dev/null +++ b/Runtime/Utilities/ArrayUtility.h @@ -0,0 +1,9 @@ +#ifndef ARRAY_UTILITY_H +#define ARRAY_UTILITY_H + + +// Element count of a static array +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + + +#endif diff --git a/Runtime/Utilities/BitSetSerialization.h b/Runtime/Utilities/BitSetSerialization.h new file mode 100644 index 0000000..1ac2eac --- /dev/null +++ b/Runtime/Utilities/BitSetSerialization.h @@ -0,0 +1,78 @@ +#include "dynamic_bitset.h" +#include "Runtime/Serialize/TypeTree.h" +#include "Runtime/Serialize/SwapEndianArray.h" + +template<> +class SerializeTraits<dynamic_bitset>: public SerializeTraitsBase<dynamic_bitset> +{ + public: + + typedef dynamic_bitset value_type; + + inline static const char* GetTypeString (value_type*) { return "bitset"; } + inline static bool IsAnimationChannel () { return false; } + inline static bool MightContainPPtr () { return false; } + inline static bool AllowTransferOptimization () { return false; } + + template<class TransferFunction> inline + static void Transfer (value_type& data, TransferFunction& transfer) + { + SInt32 bitCount = data.size (); + transfer.Transfer (bitCount, "bitCount"); + + unsigned byteSize = data.num_blocks () * sizeof (value_type::block_type); + transfer.TransferTypeless (&byteSize, "bitblocks"); + AssertIf (sizeof (value_type::block_type) != 4); + AssertIf (byteSize % 4 != 0); + + if (transfer.IsReading ()) + { + data.resize (bitCount); + transfer.TransferTypelessData (byteSize, data.m_bits); + if (transfer.ConvertEndianess ()) + SwapEndianArray (data.m_bits, sizeof (value_type::block_type), byteSize / 4); + data.m_zero_unused_bits (); + } + else if (transfer.IsWriting ()) + { + value_type::block_type* writeData = data.m_bits; + if (transfer.ConvertEndianess ()) + { + writeData = (value_type::block_type*)UNITY_MALLOC (kMemTempAlloc, byteSize); + memcpy (writeData, data.m_bits, byteSize); + SwapEndianArray (writeData, sizeof (value_type::block_type), byteSize / 4); + } + + AssertIf (data.num_blocks () != byteSize / 4); + transfer.TransferTypelessData (byteSize, writeData); + + if (transfer.ConvertEndianess ()) + UNITY_FREE (kMemTempAlloc, writeData); + } + else + transfer.TransferTypelessData (byteSize, NULL); + } + // Deque<bool> converter + template<class TransferFunction> + static bool Convert (value_type& data, TransferFunction& transfer) + { + const TypeTree& oldTypeTree = transfer.GetActiveOldTypeTree (); + const std::string& oldType = transfer.GetActiveOldTypeTree ().m_Type; + if ((oldType == "vector" || oldType == "deque") && GetElementTypeFromContainer (oldTypeTree).m_Type == "bool") + { + std::deque<bool> dequeBool; + transfer.TransferSTLStyleArray (dequeBool); + data.resize (dequeBool.size ()); + + std::deque<bool>::iterator d = dequeBool.begin (); + for (int i=0;i<data.size ();i++) + { + data[i] = *d; + d++; + } + return true; + } + else + return false; + } +}; diff --git a/Runtime/Utilities/BitUtility.h b/Runtime/Utilities/BitUtility.h new file mode 100644 index 0000000..774d6e0 --- /dev/null +++ b/Runtime/Utilities/BitUtility.h @@ -0,0 +1,189 @@ +#ifndef BITUTILITY_H +#define BITUTILITY_H + +#include <limits.h> + +// index of the most significant bit in the mask + +const char gHighestBitLut[] = {-1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3}; + +inline int HighestBit(UInt32 mask) +{ +#ifdef SN_TARGET_PS3 + return 31 - __cntlzw(mask); +#else + int base = 0; + + if ( mask & 0xffff0000 ) + { + base = 16; + mask >>= 16; + } + if ( mask & 0x0000ff00 ) + { + base += 8; + mask >>= 8; + } + if ( mask & 0x000000f0 ) + { + base += 4; + mask >>= 4; + } + + return base + gHighestBitLut[ mask ]; +#endif +} + + +// index of the least significant bit in the mask + +const char gLowestBitLut[] = {-1,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0}; +inline int LowestBit(UInt32 mask) +{ + AssertIf (mask == 0); + + int base = 0; + + if ( !(mask & 0xffff) ) + { + base = 16; + mask >>= 16; + } + if ( !(mask & 0x00ff) ) + { + base += 8; + mask >>= 8; + } + if ( !(mask & 0x000f) ) + { + base += 4; + mask >>= 4; + } + + return base + gLowestBitLut[ mask & 15 ]; +} + +// can be optimized later +inline int AnyBitFromMask (UInt32 mask) +{ + return HighestBit (mask); +} + +// index of the first consecutiveBitCount enabled bits +// -1 if not available +inline int LowestBitConsecutive (UInt32 bitMask, int consecutiveBitCount) +{ + UInt32 tempBitMask = bitMask; + int i; + for (i=1;i<consecutiveBitCount;i++) + tempBitMask &= bitMask >> i; + + if (!tempBitMask) + return -1; + else + return LowestBit (tempBitMask); +} +/*int LowestBitConsecutive ( u_int value, u_int consecutiveBitCount ) +{ + u_int mask = (1 << consecutiveBitCount) - 1; + u_int notValue = value ^ 0xffffffff; + u_int workingMask = mask; + u_int prevMask = 0; + int match = notValue & workingMask; + u_int shift = 1; + while ( (match != 0) && (prevMask < workingMask) ) + { + shift = 2*u_int(match & -match); + prevMask = workingMask; + workingMask = mask * shift; + match = notValue & workingMask; + } + if ( prevMask < workingMask ) + { + return LowestBit( shift ); + } + else + { + return -1; + } +}*/ + +// number of set bits in the 32 bit mask +inline int BitsInMask (UInt32 v) +{ + // From http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + // This variant about 30% faster on 360 than what was here before. + v = v - ((v >> 1) & 0x55555555); + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); + return ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; +} + +// number of set bits in the 64 bit mask +inline int BitsInMask64 (UInt64 v) +{ + // From http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + v = v - ((v >> 1) & (UInt64)~(UInt64)0/3); + v = (v & (UInt64)~(UInt64)0/15*3) + ((v >> 2) & (UInt64)~(UInt64)0/15*3); + v = (v + (v >> 4)) & (UInt64)~(UInt64)0/255*15; + return (UInt64)(v * ((UInt64)~(UInt64)0/255)) >> (sizeof(UInt64) - 1) * CHAR_BIT; +} + +// reverse bit order +inline void ReverseBits(UInt32& mask) +{ + mask = ((mask >> 1) & 0x55555555) | ((mask << 1) & 0xaaaaaaaa); + mask = ((mask >> 2) & 0x33333333) | ((mask << 2) & 0xcccccccc); + mask = ((mask >> 4) & 0x0f0f0f0f) | ((mask << 4) & 0xf0f0f0f0); + mask = ((mask >> 8) & 0x00ff00ff) | ((mask << 8) & 0xff00ff00); + mask = ((mask >> 16) & 0x0000ffff) | ((mask << 16) & 0xffff0000) ; +} + +// is value a power-of-two +inline bool IsPowerOfTwo(UInt32 mask) +{ + return (mask & (mask-1)) == 0; +} + +// return the next power-of-two of a 32bit number +inline UInt32 NextPowerOfTwo(UInt32 v) +{ + v -= 1; + v |= v >> 16; + v |= v >> 8; + v |= v >> 4; + v |= v >> 2; + v |= v >> 1; + return v + 1; +} + +// return the closest power-of-two of a 32bit number +inline UInt32 ClosestPowerOfTwo(UInt32 v) +{ + UInt32 nextPower = NextPowerOfTwo (v); + UInt32 prevPower = nextPower >> 1; + if (v - prevPower < nextPower - v) + return prevPower; + else + return nextPower; +} + +inline UInt32 ToggleBit (UInt32 bitfield, int index) +{ + AssertIf (index < 0 || index >= 32); + return bitfield ^ (1 << index); +} + +// Template argument must be a power of 2 +template<int n> +struct StaticLog2 +{ + static const int value = StaticLog2<n/2>::value + 1; +}; + +template<> +struct StaticLog2<1> +{ + static const int value = 0; +}; + +#endif diff --git a/Runtime/Utilities/CStringHash.h b/Runtime/Utilities/CStringHash.h new file mode 100644 index 0000000..a4b58ef --- /dev/null +++ b/Runtime/Utilities/CStringHash.h @@ -0,0 +1,52 @@ +#ifndef CSTRINGHASH_H +#define CSTRINGHASH_H +#include <functional> + +struct hash_cstring : std::unary_function<const char*, std::size_t> +{ + unsigned operator ()(const char* key) const + { + unsigned h = 0; + const unsigned sr = 8 * sizeof (unsigned) - 8; + const unsigned mask = 0xF << (sr + 4); + while (*key != '\0') + { + h = (h << 4) + *key; + std::size_t g = h & mask; + h ^= g | (g >> sr); + key++; + } + return h; + } +}; + +struct equal_cstring : std::binary_function<char*, char*, std::size_t> +{ + bool operator () (char* lhs, char* rhs) const + { + while (*lhs != '\0') + { + if (*lhs != *rhs) + return false; + lhs++; rhs++; + } + return *lhs == *rhs; + } +}; + +struct smaller_cstring : std::binary_function<const char*, const char*, std::size_t> +{ + bool operator () (const char* lhs, const char* rhs) const { return strcmp (lhs, rhs) < 0; } +}; + +struct compare_cstring : public std::binary_function<const char*, const char*, bool> +{ + bool operator ()(const char* lhs, const char* rhs) const { return strcmp (lhs, rhs) < 0; } +}; + +struct compare_string_insensitive : public std::binary_function<const std::string, const std::string, bool> +{ + bool operator ()(const std::string& lhs, const std::string& rhs) const { return StrICmp (lhs, rhs) < 0; } +}; + +#endif diff --git a/Runtime/Utilities/CopyPaste.h b/Runtime/Utilities/CopyPaste.h new file mode 100644 index 0000000..8c9e299 --- /dev/null +++ b/Runtime/Utilities/CopyPaste.h @@ -0,0 +1,10 @@ +#ifndef COPYPASTE_H +#define COPYPASTE_H + +/// Get the system-wide copy buffer for pasting into a textfield +std::string GetCopyBuffer (); + +/// Set the system-wide copybuffer for pasting from a textfield +void SetCopyBuffer (const std::string &utf8string); + +#endif diff --git a/Runtime/Utilities/DateTime.cpp b/Runtime/Utilities/DateTime.cpp new file mode 100644 index 0000000..b95e06c --- /dev/null +++ b/Runtime/Utilities/DateTime.cpp @@ -0,0 +1,45 @@ +#include "UnityPrefix.h" +#include "DateTime.h" +#include "Runtime/Serialize/SwapEndianBytes.h" + + +DateTime::DateTime () +{ + highSeconds = 0; + lowSeconds = 0; + fraction = 0; +} + +bool operator < (const DateTime& d0, const DateTime& d1) +{ + if (d0.highSeconds < d1.highSeconds) + return true; + else if (d0.highSeconds > d1.highSeconds) + return false; + + if (d0.lowSeconds < d1.lowSeconds) + return true; + else if (d0.lowSeconds > d1.lowSeconds) + return false; + + return d0.fraction < d1.fraction; +} + +bool operator == (const DateTime& d0, const DateTime& d1) +{ + if (d0.highSeconds != d1.highSeconds) + return false; + + if (d0.lowSeconds != d1.lowSeconds) + return false; + + return d0.fraction == d1.fraction; +} + + +void ByteSwapDateTime (DateTime& dateTime) +{ + SwapEndianBytes(dateTime.highSeconds); + SwapEndianBytes(dateTime.fraction); + SwapEndianBytes(dateTime.lowSeconds); +} diff --git a/Runtime/Utilities/DateTime.h b/Runtime/Utilities/DateTime.h new file mode 100644 index 0000000..b747628 --- /dev/null +++ b/Runtime/Utilities/DateTime.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Runtime/Serialize/SerializeUtility.h" + +struct DateTime +{ + // We are wasting memory and serialization + // in assetdatabase is wrong because of the alignment! + // We do this change in 1.5 because it will force a rebuild of all assets + UInt16 highSeconds; + UInt16 fraction; + UInt32 lowSeconds; + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (DateTime) + + DateTime (); + + friend bool operator < (const DateTime& d0, const DateTime& d1); + friend bool operator == (const DateTime& d0, const DateTime& d1); + friend bool operator != (const DateTime& d0, const DateTime& d1) { return !(d0 == d1); } +}; + +template<class TransferFunction> +void DateTime::Transfer (TransferFunction& transfer) +{ + TRANSFER (highSeconds); + TRANSFER (fraction); + TRANSFER (lowSeconds); +} + +void ByteSwapDateTime (DateTime& dateTime); diff --git a/Runtime/Utilities/EditorPrefsTests.cpp b/Runtime/Utilities/EditorPrefsTests.cpp new file mode 100644 index 0000000..3d066f0 --- /dev/null +++ b/Runtime/Utilities/EditorPrefsTests.cpp @@ -0,0 +1,160 @@ +#include "UnityPrefix.h" +#include "Runtime/Utilities/PlayerPrefs.h" + +#if UNITY_EDITOR && ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" + +#if UNITY_OSX + // Defined in EditorPrefsTests.mm + void InitNSAutoreleasePool(); + void ReleaseNSAutoreleasePool(); + + #define INIT_TEST InitNSAutoreleasePool() + #define RELEASE_TEST ReleaseNSAutoreleasePool() +#else + #define INIT_TEST + #define RELEASE_TEST +#endif + +// We test PlayerPrefs in Integration tests +// EditorPrefs are not exposed on C# side, so we need to tests them in C++ + +SUITE (EditorPrefsTests) +{ + TEST(TestEditorPrefs) + { + INIT_TEST; + + const std::string + kBoolKeyName = "someBool", + kIntKeyName = "someInt", + kFloatKeyName = "someFloat", + kStringKeyName = "someString", + kNonExistingKeyName = "someName"; + const std::string + kBoolKeyNameCaps = "someBOOL", + kIntKeyNameCaps = "someINT", + kFloatKeyNameCaps = "someFLOAT", + kStringKeyNameCaps = "someSTRING"; + const bool kStoredBool = true, kStoredBoolCaps = false, kDefaultBool = false; + const int kStoredInt = 3, kStoredIntCaps = 4, kDefaultInt = 7; + const float kStoredFloat = 5.7f, kStoredFloatCaps = 6.23f, kDefaultFloat = 7.17f; + const std::string kStoredString = "This is the string", kStoredStringCaps = "CAPs string", kDefaultString = "Did not find meh"; + + // Making sure there are no permanently stored keys + EditorPrefs::DeleteKey(kBoolKeyName); + EditorPrefs::DeleteKey(kIntKeyName); + EditorPrefs::DeleteKey(kFloatKeyName); + EditorPrefs::DeleteKey(kStringKeyName); + + EditorPrefs::DeleteKey(kBoolKeyNameCaps); + EditorPrefs::DeleteKey(kIntKeyNameCaps); + EditorPrefs::DeleteKey(kFloatKeyNameCaps); + EditorPrefs::DeleteKey(kStringKeyNameCaps); + + CHECK(EditorPrefs::SetBool(kBoolKeyName, kStoredBool)); + CHECK(EditorPrefs::SetInt(kIntKeyName, kStoredInt)); + CHECK(EditorPrefs::SetFloat(kFloatKeyName, kStoredFloat)); + CHECK(EditorPrefs::SetString(kStringKeyName, kStoredString)); + + CHECK_EQUAL(EditorPrefs::GetBool(kBoolKeyName, kDefaultBool), kStoredBool); + CHECK_EQUAL(EditorPrefs::GetInt(kBoolKeyName, kDefaultInt), 1); // SetBool fallsback to SetInt, so this call suceeds getting value of kBoolKeyName + CHECK_EQUAL(EditorPrefs::GetFloat(kBoolKeyName, kDefaultFloat), kDefaultFloat); + CHECK_EQUAL(EditorPrefs::GetString(kBoolKeyName, kDefaultString), kDefaultString); + + CHECK_EQUAL(EditorPrefs::GetBool(kIntKeyName, kDefaultBool), true); // GetBool fallsback to GetInt, so this call succeeds getting value of kIntKeyName + CHECK_EQUAL(EditorPrefs::GetInt(kIntKeyName, kDefaultInt), kStoredInt); + CHECK_EQUAL(EditorPrefs::GetFloat(kIntKeyName, kDefaultFloat), kDefaultFloat); + CHECK_EQUAL(EditorPrefs::GetString (kIntKeyName, kDefaultString), kDefaultString); + + CHECK_EQUAL(EditorPrefs::GetBool(kFloatKeyName, kDefaultBool), kDefaultBool); + CHECK_EQUAL(EditorPrefs::GetInt (kFloatKeyName, kDefaultInt), kDefaultInt); + CHECK_EQUAL(EditorPrefs::GetFloat (kFloatKeyName, kDefaultFloat), kStoredFloat); + CHECK_EQUAL(EditorPrefs::GetString (kFloatKeyName, kDefaultString), kDefaultString); + + CHECK_EQUAL(EditorPrefs::GetBool(kStringKeyName, kDefaultBool), kDefaultBool); + CHECK_EQUAL(EditorPrefs::GetInt (kStringKeyName, kDefaultInt), kDefaultInt); + CHECK_EQUAL(EditorPrefs::GetFloat (kStringKeyName, kDefaultFloat), kDefaultFloat); + CHECK_EQUAL(EditorPrefs::GetString (kStringKeyName, kDefaultString), kStoredString); + + CHECK_EQUAL(EditorPrefs::GetBool(kNonExistingKeyName, kDefaultBool), kDefaultBool); + CHECK_EQUAL(EditorPrefs::GetInt (kNonExistingKeyName, kDefaultInt), kDefaultInt); + CHECK_EQUAL(EditorPrefs::GetFloat (kNonExistingKeyName, kDefaultFloat), kDefaultFloat); + CHECK_EQUAL(EditorPrefs::GetString (kNonExistingKeyName, kDefaultString), kDefaultString); + + CHECK(EditorPrefs::HasKey(kBoolKeyName)); + CHECK(EditorPrefs::HasKey(kIntKeyName)); + CHECK(EditorPrefs::HasKey(kFloatKeyName)); + CHECK(EditorPrefs::HasKey(kStringKeyName)); + CHECK(!EditorPrefs::HasKey(kNonExistingKeyName)); + + // Case sensitivity tests + CHECK_EQUAL(EditorPrefs::GetBool(kBoolKeyNameCaps, kDefaultBool), kDefaultBool); + CHECK_EQUAL(EditorPrefs::GetInt(kIntKeyNameCaps, kDefaultInt), kDefaultInt); + CHECK_EQUAL(EditorPrefs::GetFloat(kFloatKeyNameCaps, kDefaultFloat), kDefaultFloat); + CHECK_EQUAL(EditorPrefs::GetString(kStringKeyNameCaps, kDefaultString), kDefaultString); + + CHECK(!EditorPrefs::HasKey(kBoolKeyNameCaps)); + CHECK(!EditorPrefs::HasKey(kIntKeyNameCaps)); + CHECK(!EditorPrefs::HasKey(kFloatKeyNameCaps)); + CHECK(!EditorPrefs::HasKey(kStringKeyNameCaps)); + + CHECK(EditorPrefs::SetBool(kBoolKeyNameCaps, kStoredBoolCaps)); + CHECK(EditorPrefs::SetInt(kIntKeyNameCaps, kStoredIntCaps)); + CHECK(EditorPrefs::SetFloat(kFloatKeyNameCaps, kStoredFloatCaps)); + CHECK(EditorPrefs::SetString(kStringKeyNameCaps, kStoredStringCaps)); + + CHECK_EQUAL(EditorPrefs::GetBool(kBoolKeyName, kDefaultBool), kStoredBool); + CHECK_EQUAL(EditorPrefs::GetInt(kIntKeyName, kDefaultInt), kStoredInt); + CHECK_EQUAL(EditorPrefs::GetFloat(kFloatKeyName, kDefaultFloat), kStoredFloat); + CHECK_EQUAL(EditorPrefs::GetString(kStringKeyName, kDefaultString), kStoredString); + + CHECK_EQUAL(EditorPrefs::GetBool(kBoolKeyNameCaps, kDefaultBool), kStoredBoolCaps); + CHECK_EQUAL(EditorPrefs::GetInt(kIntKeyNameCaps, kDefaultInt), kStoredIntCaps); + CHECK_EQUAL(EditorPrefs::GetFloat(kFloatKeyNameCaps, kDefaultFloat), kStoredFloatCaps); + CHECK_EQUAL(EditorPrefs::GetString(kStringKeyNameCaps, kDefaultString), kStoredStringCaps); + + RELEASE_TEST; + } + + + // Set value using one type, then set with another type; Check values for both types + #define TEST_OVERRIDE(TypeName1, TypeName2, Expected1, Expected2) \ + EditorPrefs::DeleteKey(kKeyName); \ + CHECK(EditorPrefs::Set##TypeName1(kKeyName, kStored##TypeName1)); \ + CHECK(EditorPrefs::Set##TypeName2(kKeyName, kStored##TypeName2)); \ + CHECK_EQUAL(EditorPrefs::Get##TypeName1(kKeyName, kDefault##TypeName1), Expected1); \ + CHECK_EQUAL(EditorPrefs::Get##TypeName2(kKeyName, kDefault##TypeName2), Expected2); \ + + TEST(EditorPrefsOverriding) + { + INIT_TEST; + + const std::string kKeyName = "MyKey"; + const bool kStoredBool = true, kDefaultBool = false; + const int kStoredInt = 3, kDefaultInt = 7; + const float kStoredFloat = 5.7f, kDefaultFloat = 7.17f; + const std::string kStoredString = "This is the string", kDefaultString = "Did not find meh"; + + TEST_OVERRIDE(Bool, Int, true, kStoredInt); // ints and bool are stored the same, so value is shared + TEST_OVERRIDE(Bool, Float, kDefaultBool, kStoredFloat); + TEST_OVERRIDE(Bool, String, kDefaultBool, kStoredString); + + TEST_OVERRIDE(Int, Bool, 1, true); // ints and bool are stored the same, so value is shared + TEST_OVERRIDE(Int, Float, kDefaultInt, kStoredFloat); + TEST_OVERRIDE(Int, String, kDefaultInt, kStoredString); + + TEST_OVERRIDE(Float, Bool, kDefaultFloat, kStoredBool); + TEST_OVERRIDE(Float, Int, kDefaultFloat, kStoredInt); + TEST_OVERRIDE(Float, String, kDefaultFloat, kStoredString); + + TEST_OVERRIDE(String, Bool, kDefaultString, kStoredBool); + TEST_OVERRIDE(String, Int, kDefaultString, kStoredInt); + TEST_OVERRIDE(String, Float, kDefaultString, kStoredFloat); + + RELEASE_TEST; + } +} + +#endif diff --git a/Runtime/Utilities/EndianHelper.h b/Runtime/Utilities/EndianHelper.h new file mode 100644 index 0000000..42df0ac --- /dev/null +++ b/Runtime/Utilities/EndianHelper.h @@ -0,0 +1,148 @@ +#ifndef UNITY_ENDIAN_HELPER_H_ +#define UNITY_ENDIAN_HELPER_H_ + + +enum EndianMode { + kEndianDirect = 0, + kEndianConvert = 1, +#if UNITY_BIG_ENDIAN + kHostEndian = kEndianDirect, + kTargetEndian = kEndianDirect, +#else + kHostEndian = kEndianConvert, + kTargetEndian = kEndianConvert, +#endif +}; + + template<EndianMode Endian> + struct EndianHelper; + + template<> + struct EndianHelper<kEndianDirect> + { + static UInt16 Load( UInt16 const* p ) { + return *p; + } + + static UInt32 Load( UInt32 const* p ) { + return *p; + } + + template<int Offset> + static void Store( UInt16* p, UInt16 arg ) { + *reinterpret_cast<UInt16*>( ((intptr_t)p + Offset) ) = arg; + } + + template<int Offset> + static void Store( UInt32* p, UInt32 arg ) { + *reinterpret_cast<UInt32*>( ((intptr_t)p + Offset) ) = arg; + } + + }; + + template<> + struct EndianHelper<kEndianConvert> + { + static UInt16 Load( register UInt16 const* p ) { +//#if defined(__native_client__) || (defined(__GNUC__) && !defined(__APPLE__)) + return ((*p&0xff00U)>>8) | ((*p&0x00ffU)<<8); +//#elif defined(__ppc__) +// register UInt16 temp; +// __asm { +// lhbrx temp, 0, p +// } +// return temp; +//#elif defined(__i386) || defined(_M_IX86) +// register UInt16 temp = *p; +// __asm { +// mov ax, temp +// xchg ah, al +// mov temp, ax +// } +// return temp; +//#else +//#error "Unsupported architecture" +//#endif + } + + static UInt32 Load( register UInt32 const* p ) { +//#if defined(__native_client__) || (defined(__GNUC__) && !defined(__APPLE__)) + return + ((*p&0xff000000U)>>24) | + ((*p&0x00ff0000U)>>8) | + ((*p&0x0000ff00U)<<8) | + ((*p&0x000000ffU)<<24) + ; +//#elif defined(__ppc__) +// register UInt32 temp; +// __asm { +// lwbrx r4, 0, p +// } +// return temp; +//#elif defined(__i386) || defined(_M_IX86) +// register UInt32 temp = *p; +// __asm { +// mov eax, temp +// bswap eax +// mov temp, eax +// } +// return temp; +//#else +//#error "Unsupported architecture" +//#endif + } + + // Offset is in bytes + template<int Offset> + static void Store( register UInt16* p, register UInt16 arg ) { +//#if defined(__native_client__) || (defined(__GNUC__) && !defined(__APPLE__)) + UInt16 temp = ((arg&0xff00U)>>8) | ((arg&0x00ffU)<<8); + *(UInt16*)((UInt8*)p + Offset) = temp; +//#elif defined(__ppc__) +// register short int ofs = Offset; +// __asm { +// sthbrx arg, ofs, p +// } +//#elif defined(__i386) || defined(_M_IX86) +// register void* pp = (void*)((uintptr_t)p + Offset); +// __asm { +// mov ax, arg +// xchg ah, al +// mov ecx, dword ptr [pp] +// mov word ptr [ecx], ax +// } +//#else +//#error "Unsupported architecture" +//#endif + } + + template<int Offset> + static void Store( register UInt32* p, register UInt32 arg ) { +//#if defined(__native_client__) || (defined(__GNUC__) && !defined(__APPLE__)) + UInt32 temp = + ((arg&0xff000000U)>>24) | + ((arg&0x00ff0000U)>>8) | + ((arg&0x0000ff00U)<<8) | + ((arg&0x000000ffU)<<24) + ; + *(UInt32*)((UInt8*)p + Offset) = temp; +//#elif defined(__ppc__) +// register short int ofs = Offset; +// __asm { +// stwbrx arg, ofs, p +// } +//#elif defined(__i386) || defined(_M_IX86) +// register void* pp = (void*)((uintptr_t)p + Offset); +// __asm { +// mov eax, arg +// bswap eax +// mov ecx, dword ptr [pp] +// mov word ptr [ecx], eax +// } +//#else +//#error "Unsupported architecture" +//#endif + } + }; + +#endif // UNITY_ENDIAN_HELPER_H_ diff --git a/Runtime/Utilities/EnumFlags.h b/Runtime/Utilities/EnumFlags.h new file mode 100644 index 0000000..61d323b --- /dev/null +++ b/Runtime/Utilities/EnumFlags.h @@ -0,0 +1,13 @@ +#ifndef ENUM_FLAGS_H +#define ENUM_FLAGS_H + +#define ENUM_FLAGS(T) \ +inline T operator |(const T s, const T e) { return (T)((unsigned)s | e); } \ +inline T &operator |=(T &s, const T e) { return s = s | e; } \ +inline T operator &(const T s, const T e) { return (T)((unsigned)s & e); } \ +inline T &operator &=(T &s, const T e) { return s = s & e; } \ +inline T operator ^(const T s, const T e) { return (T)((unsigned)s ^ e); } \ +inline T &operator ^=(T &s, const T e) { return s = s ^ e; } \ +inline T operator ~(const T s) { return (T)~(unsigned)s; } + +#endif diff --git a/Runtime/Utilities/ErrorExit.cpp b/Runtime/Utilities/ErrorExit.cpp new file mode 100644 index 0000000..414aa06 --- /dev/null +++ b/Runtime/Utilities/ErrorExit.cpp @@ -0,0 +1,165 @@ +#include "UnityPrefix.h" +#include "ErrorExit.h" +#include "Runtime/Misc/ReproductionLog.h" +#if SUPPORT_ERROR_EXIT +#include "Runtime/Threads/ThreadSpecificValue.h" +#include "Stacktrace.h" + +#if UNITY_WIN + +#include "Configuration/UnityConfigureOther.h" +#include "Runtime/Mono/MonoIncludes.h" + +#include "PlatformDependent/Win/WinUtils.h" + +#if !UNITY_WIN_ENABLE_CRASHHANDLER +#error "UNITY_WIN_ENABLE_CRASHHANDLER must be defined" +#endif + +#include "../../Tools/BugReporterWin/lib/CrashHandler.h" +extern CrashHandler *gUnityCrashHandler; + +#endif + +ExitErrorCode gExitErrorCode = kErrorNone; +bool gExitErrorDidCleanup = false; +#if !UNITY_WIN +static UNITY_TLS_VALUE(jmp_buf*) gJumpBuffer; +#endif + +ExitErrorCode GetExitErrorCode() +{ + return gExitErrorCode; +} + +#if UNITY_PEPPER +#define STACKTRACE() "" +#else +#define STACKTRACE() GetStacktrace().c_str() +#endif + +void ExitWithErrorCode(ExitErrorCode error) +{ + printf_console("Exit with error code: %d\n", error); + printf_console("\n========== OUTPUTING STACK TRACE ==================\n\n"); + printf_console("%s\n", STACKTRACE()); + printf_console("\n========== END OF STACKTRACE ===========\n\n"); + + #if SUPPORT_REPRODUCE_LOG + if (RunningReproduction()) + { + ErrorString("ExitWithErrorCode invoked, exiting application because we are in reproduction mode."); + ReproductionExitPlayer(1); + } + #endif + + #if !UNITY_RELEASE + printf_console("\n*** DEBUGMODE -> ExitWithError code calling abort for debugging ease *** "); + abort(); + #endif + + gExitErrorCode = error; + + #if !UNITY_WIN + + jmp_buf *buf = gJumpBuffer; + + if (buf) + { + printf_console("Got error %d. Bailing out.\n", error); + longjmp (*buf, 1); + } + else + { + printf_console("Got error %d. Cannot exit at this point.\n", error); + } + + #endif +} + +#if !UNITY_WIN + +void InsertThreadExitPoint(jmp_buf *buf) +{ + gJumpBuffer = buf; +} + +AutoRemoveEntryPoint::AutoRemoveEntryPoint (jmp_buf *buf) +{ + if (gJumpBuffer) + m_ShouldRemove = false; + else + { + m_ShouldRemove = true; + gJumpBuffer = buf; + } +} + +AutoRemoveEntryPoint::~AutoRemoveEntryPoint () +{ + if (m_ShouldRemove) + gJumpBuffer = NULL; +} + +#endif + +const char* GetExitErrorString(ExitErrorCode err) +{ + switch(err) + { + case kErrorSecurityBreach: + return "The content was stopped because it\nattempted an insecure operation."; + case kErrorFatalException: + return "The content was stopped because a fatal\ncontent error has been detected."; + case kErrorNoSSE2Architecture: + return "Unity Web Player requires an SSE2 capable CPU."; + case kErrorIncompatibleRuntimeVersion: + return "The Unity Web Player content which you are trying to load was built with an older version of the Unity Editor, and is incompatible with this runtime."; + case kErrorUnsupportedGPU: + return "Unity Web Player requires DX9 level graphics card.\nMake sure you have graphics card drivers installed."; + default: + return NULL; + } +} + +#if UNITY_WIN + +extern LPTOP_LEVEL_EXCEPTION_FILTER exceptionFilter; +int HandleSignal( EXCEPTION_POINTERS* ep ); + +DWORD OnExcept(DWORD code, LPEXCEPTION_POINTERS exception) +{ + __try + { + DWORD result; + if( NULL != exceptionFilter ) + { + #if WEBPLUG + winutils::ProcessInternalCrash(exception, false); + #endif + + result = exceptionFilter(exception); + } + else + { + result = mono_unity_seh_handler(exception); + } + + if (EXCEPTION_CONTINUE_EXECUTION != result) + { + gExitErrorCode = kErrorFatalException; + result = EXCEPTION_EXECUTE_HANDLER; + } + + return result; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + gExitErrorCode = kErrorFatalException; + return EXCEPTION_EXECUTE_HANDLER; + } +} + +#endif +#endif + diff --git a/Runtime/Utilities/ErrorExit.h b/Runtime/Utilities/ErrorExit.h new file mode 100644 index 0000000..b531ace --- /dev/null +++ b/Runtime/Utilities/ErrorExit.h @@ -0,0 +1,77 @@ +#ifndef ERROREXIT_H +#define ERROREXIT_H + +#include "Configuration/UnityConfigure.h" + +#if SUPPORT_ERROR_EXIT + +enum ExitErrorCode +{ + kErrorNone = 0, + kErrorSecurityBreach = 1, + kErrorFatalException = 2, + kErrorNoSSE2Architecture = 3, + kErrorIncompatibleRuntimeVersion = 4, + kErrorUnsupportedGPU = 5, +}; + +extern ExitErrorCode gExitErrorCode; + +#if !UNITY_WIN +#include <setjmp.h> +void InsertThreadExitPoint(jmp_buf *buf); +class AutoRemoveEntryPoint { + bool m_ShouldRemove; +public: + AutoRemoveEntryPoint (jmp_buf *buf); + ~AutoRemoveEntryPoint (); +}; +#endif +void ExitWithErrorCode(ExitErrorCode error); +ExitErrorCode GetExitErrorCode(); +const char *GetExitErrorString(ExitErrorCode err); + +#if UNITY_WIN + +DWORD OnExcept(DWORD code, LPEXCEPTION_POINTERS exception); + +#define UNITY_ENTRY_POINT(x) if(GetExitErrorCode()) return x; __try { +#define UNITY_ENTRY_POINT_NO_RETURN_VALUE() if(GetExitErrorCode()) return; __try { +#define UNITY_ENTRY_POINT_SKIP() if(!GetExitErrorCode()) { __try { +#define UNITY_EXIT_POINT(x) } __except (OnExcept(GetExceptionCode(), GetExceptionInformation())) { return x; } +#define UNITY_EXIT_POINT_NO_RETURN_VALUE() } __except (OnExcept(GetExceptionCode(), GetExceptionInformation())) { return; } +#define UNITY_EXIT_POINT_SKIP() } __except (OnExcept(GetExceptionCode(), GetExceptionInformation())) { } } + +#define ERROR_EXIT_THREAD_ENTRY() \ + __try \ + { +#define ERROR_EXIT_THREAD_EXIT() \ + } \ + __except (OnExcept(GetExceptionCode(), \ + GetExceptionInformation())) \ + { \ + /* do nothing */ \ + } + +#else +#define UNITY_ENTRY_POINT(x) if(GetExitErrorCode()) return x; jmp_buf buf; if (setjmp (buf)) return x; AutoRemoveEntryPoint entry(&buf); +#define UNITY_ENTRY_POINT_NO_RETURN_VALUE() if(GetExitErrorCode()) return; jmp_buf buf; if (setjmp (buf)) return; AutoRemoveEntryPoint entry(&buf); +#define UNITY_EXIT_POINT(x) +#define UNITY_EXIT_POINT_NO_RETURN_VALUE() + +#define ERROR_EXIT_THREAD_ENTRY() \ + jmp_buf buf; \ + if (!setjmp (buf)) \ + { \ + InsertThreadExitPoint (&buf); +#define ERROR_EXIT_THREAD_EXIT() \ + } + +#endif + +#else +#define ExitWithErrorCode(x) +#define GetExitErrorCode() kErrorNone +#endif + +#endif diff --git a/Runtime/Utilities/File.cpp b/Runtime/Utilities/File.cpp new file mode 100644 index 0000000..f93e2df --- /dev/null +++ b/Runtime/Utilities/File.cpp @@ -0,0 +1,831 @@ +#include "UnityPrefix.h" +#include "File.h" +#include "PathNameUtility.h" +#include "PlatformDependent/Win/unistd.h" +#include <stdio.h> +#include <string.h> +#include <errno.h> +#if UNITY_EDITOR +#include "FileUtilities.h" +#include "Editor/Platform/Interface/EditorUtility.h" +#endif +#include "Runtime/Scripting/ScriptingUtility.h" +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_FLASH || UNITY_WEBGL || UNITY_TIZEN +#include <sys/types.h> +#include <sys/stat.h> +#include "dirent.h" +#endif +#if UNITY_BB10 +#include <unistd.h> +#include <dirent.h> +#endif +#include "Runtime/Threads/Thread.h" + +// This is File implementation for OSX and possibly others. Windows implementation is in PlatformDependent +#if UNITY_WIN +#error "Windows implementation of File is not here!" +#endif + +using namespace std; + +#if UNITY_EDITOR +string GetFormattedFileError (int error, const std::string& operation); +#endif + +static string gCurrentDirectory; + +static FILE* OpenFileWithPath( const string& path, File::Permission permission ) +{ +#if SUPPORT_DIRECT_FILE_ACCESS + const char* fileMode = NULL; + switch (permission) { + case File::kReadPermission: + fileMode="rb"; + break; + case File::kWritePermission: + fileMode="wb"; + break; + case File::kReadWritePermission: + fileMode="r+b"; + break; + case File::kAppendPermission: + fileMode="ab"; + break; + } + + return fopen( PathToAbsolutePath(path).c_str(), fileMode ); + +#else //SUPPORT_DIRECT_FILE_ACCESS + + ErrorString("No file access allowed!"); + return NULL; +#endif +} + +bool IsAbsoluteFilePath( const std::string& path ) +{ + if( path.empty() ) + return false; + + if( path[0] == kPlatformPathNameSeparator ) + return true; // paths starting with separator are absolute + + return false; +} + + +string PathToAbsolutePath (const string& path) +{ + if( IsAbsoluteFilePath(path) ) + return path; + else + return AppendPathName (File::GetCurrentDirectory (), path); +} + +bool ReadFromFile (const string& pathName, void *data, int fileStart, int byteLength) +{ + FILE* file = OpenFileWithPath( pathName, File::kReadPermission ); + if (file == NULL) + return false; + + fseek(file, 0, SEEK_END); + + int length = ftell(file); + fseek(file, 0, SEEK_SET); + if (length < byteLength) + { + fclose(file); + return false; + } + + int readLength = fread(data, 1, byteLength, file); + + fclose(file); + + if (readLength != byteLength) + return false; + + return true; +} + + +#if !UNITY_FLASH //flash implementation in AS3Utility.cpp +bool ReadStringFromFile (InputString* outData, const string& pathName) +{ + FILE* file = OpenFileWithPath( pathName, File::kReadPermission ); + if (file == NULL) + return false; + + fseek(file, 0, SEEK_END); + + int length = ftell(file); + fseek(file, 0, SEEK_SET); + if (length < 0) + { + fclose( file ); + return false; + } + + outData->resize(length); + int readLength = fread(&*outData->begin(), 1, length, file); + + fclose(file); + + if (readLength != length) + { + outData->clear(); + return false; + } + + return true; +} +#endif + + +bool WriteBytesToFile (const void *data, int byteLength, const string& pathName) +{ + File file; + if (!file.Open(pathName, File::kWritePermission)) + return false; + + bool success = file.Write(data, byteLength); + + file.Close(); + + return success; +} + + +int GetFileLength (const string& pathName) +{ + #if UNITY_OSX || UNITY_BB10 + struct stat statbuffer; + if( stat(pathName.c_str(), &statbuffer) != 0 ) + return 0; /// return -1 for error??? + Assert (S_ISREG(statbuffer.st_mode)); + return statbuffer.st_size; + #elif UNITY_FLASH + return Ext_FileContainer_GetFileLength(pathName.c_str()); + #else + FILE* file = OpenFileWithPath( pathName, File::kReadPermission ); + if (file == NULL) + return 0; /// return -1??? + + fseek(file, 0, SEEK_END); + int length = ftell(file); + fclose(file); + + return length; + #endif +} + +File::File () { m_File = NULL;m_Position = 0; } + +File::~File () { AssertIf(m_File != NULL); } + +#if UNITY_EDITOR +string GetFormattedFileError (int error, const std::string& operation) +{ + const char* opstr = operation.c_str(); + switch (error) + { + case ENAMETOOLONG: + return Format("%s failed because file name is too long", opstr); + case ENOTDIR: + return Format("%s failed: from directory to non-directory", opstr); + case EISDIR: + return Format("%s failed: from non-directory to directory", opstr); + case EXDEV: + return Format("%s failed: to and from are on different file systems", opstr); + case EIO: + return Format("%s failed: I/O error updating directory", opstr); + case EROFS: + return Format("%s failed: read only file system", opstr); + case EFAULT: + return Format("%s failed: segmentation fault", opstr); + case EINVAL: + return Format("%s failed: from is a parent of to, or rename of . or ..", opstr); + case ENOTEMPTY: + return Format("%s failed: to is a directory and not empty", opstr); + case EPERM: + return Format("%s failed because the operation was not permitted", opstr); + case ENOENT: + return Format("%s failed because the file or directory does not exist", opstr); + case ENOMEM: + return Format("%s failed because there was not enough memory available", opstr); + case EACCES: + return Format("%s failed because permission for the file was denied", opstr); + case EEXIST: + return Format("%s failed because the file already exists", opstr); + case ENOSPC: + return Format("%s failed because there is no disk space left. Please free some disk space and continue.", opstr); + #if UNITY_OSX + case EAUTH: + return Format("%s failed because of an authentication failure", opstr); + case ENEEDAUTH: + return Format("%s failed because you need an authenticator", opstr); + case ELOOP: + return Format("%s failed: too many symbolic links", opstr); + case EDQUOT: + return Format("%s failed: quota limit reached", opstr); + #endif + default: + return Format("%s failed with error: %s", opstr, strerror(error)); + } +} + +#endif +static bool HandleFileError (const char* title, const string& operation, FILE* file) +{ + #if UNITY_EDITOR + int err = ferror(file); + clearerr(file); + + int result = DisplayDialogComplex (title, GetFormattedFileError(err, operation), "Try Again", "Cancel", "Force Quit"); + if (result == 1) + return false; + else if (result == 2) + exit(1); + else + return true; + #else + return false; + #endif +} + +bool File::Open (const std::string& path, File::Permission perm, AutoBehavior behavior) +{ + Close(); + m_Path = path; + + int retryCount = 5; + + while (true) + { + m_File = OpenFileWithPath( path, perm ); + m_Position = 0; + if (m_File != NULL) + { + if (perm == kAppendPermission) + m_Position = ftell(m_File); + return true; + } + else + { +#if SUPPORT_THREADS + if ( (behavior & kRetryOnOpenFail) && (--retryCount > 0)) + { + Thread::Sleep(0.2); + continue; + } +#endif + + if ( behavior & kSilentReturnOnOpenFail ) + return false; + + #if UNITY_EDITOR + int result = DisplayDialogComplex ("Opening file failed", GetFormattedFileError(errno, "Opening file "+path), "Try Again", "Cancel", "Force Quit"); + if (result == 1) + return false; + else if (result == 2) + exit(1); + #else + ErrorString("Failed to open file at path: " + path); + return false; + #endif + } + } + return false; +} + +bool File::Close () +{ + if (m_File != NULL) + { + if (fclose(m_File) != 0) + { + #if UNITY_EDITOR + ErrorString(GetFormattedFileError(errno, "Closing file " + m_Path)); + #elif UNITY_FLASH + //no problemo + #else + ErrorString("Closing file " + m_Path); + #endif + } + m_File = NULL; + } + + m_Path.clear(); + return true; +} + +int File::Read (void* buffer, int size) +{ + if (m_File) + { + int s = fread(buffer, 1, size, m_File); + + if (s == size || ferror(m_File) == 0) + { + m_Position += s; + return s; + } + else + { + //m_Position = -1; + if (!HandleFileError("Reading file failed", "Reading from file " + m_Path, m_File)) + return false; + + // We don't know how far the file was read. + // So we just play safe and go through the API that seeks from a specific offset + int oldPos = m_Position; + m_Position = -1; + return Read(oldPos, buffer, size); + } + } + else + { + ErrorString("Reading failed because the file was not opened"); + return 0; + } +} + +int File::Read (int position, void* buffer, int size) +{ + if (m_File) + { + while (true) + { + // Seek if necessary + if (position != m_Position) + { + if (fseek(m_File, position, SEEK_SET) != -1) + m_Position = position; + else + { + m_Position = -1; + if (!HandleFileError("Reading file failed", "Seeking in file " + m_Path, m_File )) + return 0; + + continue; + } + } + + int s = fread(buffer, 1, size, m_File); + if (s == size || ferror(m_File) == 0) + { + m_Position += s; + return s; + } + else + { + m_Position = -1; + if (!HandleFileError("Reading file failed", "Reading from file " + m_Path, m_File )) + return 0; + } + } + } + else + { + ErrorString("Reading failed because the file was not opened"); + return 0; + } + return 0; +} + + + +bool File::Write (const void* buffer, int size) +{ + if (m_File) + { + int s = fwrite(buffer, 1, size, m_File); + if (s == size) + { + m_Position += s; + return true; + } + else + { + if (!HandleFileError("Writing file failed", "Writing to file " + m_Path, m_File)) + return false; + + // We don't know how far the file was read. + // So we just play safe and go through the API that seeks from a specific offset + int oldPos = m_Position; + m_Position = -1; + return Write(oldPos, buffer, size); + } + } + else + { + ErrorString("Writing failed because the file was not opened"); + return false; + } +} + +bool File::Write (int position, const void* buffer, int size) +{ + if (m_File) + { + while (true) + { + // Seek if necessary + if (position != m_Position) + { + if (fseek(m_File, position, SEEK_SET) != -1) + m_Position = position; + else + { + m_Position = -1; + if (!HandleFileError("Writing file failed", "Seeking in file "+m_Path, m_File)) + return false; + + continue; + } + } + + int s = fwrite(buffer, 1, size, m_File); + if (s == size) + { + m_Position += s; + return true; + } + else + { + m_Position = -1; + if (!HandleFileError("Writing file failed", "Writing to file "+m_Path, m_File)) + return false; + } + } + } + else + { + ErrorString("Writing failed because the file was not opened"); + } + return false; +} + +bool File::SetFileLength (int size) +{ + return ::SetFileLength(m_Path, size); +} + +int File::GetFileLength() +{ + return ::GetFileLength(m_Path); +} + +void File::SetCurrentDirectory (const string& path) +{ + gCurrentDirectory = path; +} + +const string& File::GetCurrentDirectory () +{ + return gCurrentDirectory; +} + +void File::CleanupClass() +{ + gCurrentDirectory = string(); +} + +bool SetFileLength (const std::string& path, int size) +{ + #if UNITY_PEPPER || UNITY_FLASH || UNITY_WEBGL + ErrorString("SetFileLength not supported on this platform!"); + return false; + #else + while (true) + { + int error = truncate(path.c_str(), size); + if (error == 0) + return true; + + #if UNITY_EDITOR + int result = DisplayDialogComplex ("Writing file error", GetFormattedFileError(errno, "Resizing file " + path), "Try Again", "Cancel", "Quit"); + if (result == 1) + return false; + else if (result == 2) + exit(1); + #else + return false; + #endif + } + #endif + + return true; +} + +static bool IsFileCreatedAtAbsolutePath (const string & path) +{ + #if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_FLASH || UNITY_WEBGL || UNITY_BB10 || UNITY_TIZEN + + struct stat status; + + if (stat(path.c_str(), &status) != 0) + return false; + +#if TARGET_IPHONE_SIMULATOR + // some bad hack for simulator, if we can stat it then file exists... + return true; +#endif + + return S_ISREG (status.st_mode); + + #elif !SUPPORT_DIRECT_FILE_ACCESS + + ErrorString("No file access allowed!"); + return false; + + #else + + #error "Unsupported platform!" + + #endif +} + +#if !UNITY_FLASH //Flash implementation in AS3Utility.cpp +bool IsFileCreated (const string& path) +{ + return IsFileCreatedAtAbsolutePath (PathToAbsolutePath (path)); +} +#endif + + +#if UNITY_OSX || UNITY_IPHONE || UNITY_PEPPER || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN +bool IsFileOrDirectoryInUse ( const string& path ) +{ +#if UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN + return false; +#elif !UNITY_PEPPER + bool isUsed = false; + + if ( IsDirectoryCreated( path ) ) + { + set<string> paths; + if ( GetFolderContentsAtPath( path, paths ) ) + { + for (set<string>::iterator i = paths.begin(); i != paths.end(); i++) + { + if ( IsFileOrDirectoryInUse( *i ) ) + return true; + } + } + } + else if ( IsFileCreated( path ) ) + { + File openTest; + if ( openTest.Open( path, File::kReadPermission ) ) + { + if ( !openTest.Lock( File::kExclusive, false ) ) + isUsed = true; + openTest.Lock( File::kNone, false ); + openTest.Close(); + } + else + isUsed = true; + } + + return isUsed; +#else + ErrorString("No file access allowed!"); + return false; +#endif +} +#endif + +static bool IsDirectoryCreatedAtAbsolutePath (const string & path) +{ + #if UNITY_OSX || UNITY_LINUX || UNITY_IPHONE || UNITY_BB10 || UNITY_WEBGL || UNITY_TIZEN + struct stat status; + if (stat(path.c_str(), &status) != 0) + return false; + return S_ISDIR(status.st_mode); + + #elif UNITY_FLASH + #warning "IsDirectoryCreatedAtAbsolutePath() NOT implemented for UNITY_FLASH" + return false; + + #elif !SUPPORT_DIRECT_FILE_ACCESS + + ErrorString("No file access allowed!"); + return false; + #else + #error Unknown platform + #endif +} + +bool IsDirectoryCreated (const string& path) +{ + return IsDirectoryCreatedAtAbsolutePath (PathToAbsolutePath (path)); +} + +bool CreateDirectory (const string& pathName) +{ +#if SUPPORT_DIRECT_FILE_ACCESS && !UNITY_FLASH + if (IsFileCreated (pathName)) + return false; + else if (IsDirectoryCreated (pathName)) + return true; + + string absolutePath = PathToAbsolutePath (pathName); + +#if UNITY_EDITOR + std::string fileNamePart = GetLastPathNameComponent (absolutePath); + if (CheckValidFileNameDetail (fileNamePart) == kFileNameInvalid) + { + ErrorStringMsg ("%s is not a valid directory name. Please make sure there are no unallowed characters in the name.", fileNamePart.c_str ()); + return false; + } +#endif + + int res = mkdir(absolutePath.c_str(), 0755); + return res == 0; +#else + ErrorString("No file access allowed!"); + return false; +#endif +} + +bool DeleteFile (const string& path) +{ +#if !UNITY_PEPPER && !UNITY_FLASH && !UNITY_WEBGL + string absolutePath = PathToAbsolutePath (path); + if (IsFileCreated (absolutePath)) + { + return unlink(absolutePath.c_str()) == 0; + } + else + { + return false; + } +#else + ErrorString("No file access allowed!"); + return false; +#endif +} + +int DeleteFilesAndDirsRecursive(const string& path) +{ +#if SUPPORT_DIRECT_FILE_ACCESS && !UNITY_FLASH && !UNITY_WEBGL + int res; + + struct stat status; + res = lstat(path.c_str(), &status); + if (res != 0) + return res; + + if (S_ISDIR(status.st_mode) && !S_ISLNK(status.st_mode)) + { + DIR *dirp = opendir (path.c_str()); + if (dirp == NULL) + return -1; + + struct dirent *dp; + while ( (dp = readdir(dirp)) ) + { + if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) + { + string name = dp->d_name; + res = DeleteFilesAndDirsRecursive(AppendPathName (path, name)); + if (res != 0) + { + closedir(dirp); + return res; + } + } + } + closedir(dirp); + + res = rmdir(path.c_str()); + } + else + { + res = unlink(path.c_str()); + } + + return res; +#else + ErrorString("No file access allowed!"); + return false; +#endif +} + +bool DeleteFileOrDirectory (const string& path) +{ + int res = DeleteFilesAndDirsRecursive(PathToAbsolutePath(path)); + return res == 0; +} + +bool ShouldIgnoreFile (const char* name, size_t length) +{ + AssertIf(!name); + if (length < 1) + return true; + + // ignore hidden files + if (name[0] == '.') + return true; + // ignore CVS files + if (StrICmp(name, "cvs") == 0) + return true; + + // ignore temporary and backup files + if ((name[length-1] == '~') || (length >= 4 && StrICmp(name + length - 4, ".tmp" ) == 0)) + return true; + + return false; +} + +bool GetFolderContentsAtPath (const string& pathName, set<string>& paths) +{ +#if SUPPORT_DIRECT_FILE_ACCESS && !UNITY_FLASH + string absolutePath = PathToAbsolutePath(pathName); + DIR *dirp; + struct dirent *dp; + + if ((dirp = opendir(absolutePath.c_str())) == NULL) + return false; + + while ( (dp = readdir(dirp)) ) + { + if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) + { + if (!ShouldIgnoreFile (dp->d_name, strlen (dp->d_name))) + { + string name = dp->d_name; + paths.insert (AppendPathName (pathName, name)); + } + } + } + closedir(dirp); + return true; +#else + ErrorString("No file access allowed!"); + return false; +#endif +} + +#if UNITY_EDITOR + +bool MoveReplaceFile (const string& fromPath, const string& toPath) +{ + if( IsDirectoryCreated(fromPath) ) + { + ErrorString( Format("Path %s is a directory", fromPath.c_str()) ); + return false; + } + while (true) + { + int error = rename( PathToAbsolutePath(fromPath).c_str(), PathToAbsolutePath(toPath).c_str() ); + if( error == 0 ) + return true; + + int errorCode = errno; + if( errorCode == EXDEV ) + { + DeleteFileOrDirectory(toPath); + if (CopyFileOrDirectory(fromPath, toPath)) + { + if (DeleteFileOrDirectory(fromPath)) + return true; + } + } + + #if UNITY_EDITOR + int result = DisplayDialogComplex ("Moving file failed", GetFormattedFileError(errno, "Moving "+fromPath+" to "+toPath), "Try Again", "Cancel", "Force Quit"); + if (result == 1) + return false; + else if (result == 2) + exit(1); + #else + return false; + #endif + } + return false; +} + +#endif + +#if UNITY_OSX || UNITY_IPHONE +bool File::Lock(File::LockMode mode, bool block) +{ + int fd = fileno (m_File); + return flock (fd, mode + (block?0:LOCK_NB) ) == 0; +} + +#include <sys/resource.h> +//Local testing and various internet sources suggest that this is the absolute maximim of open files OSX can hande. +#define kMaxOpenFile 10240 + +void SetupFileLimits () +{ + struct rlimit limit; + limit.rlim_cur = kMaxOpenFile; + limit.rlim_max = kMaxOpenFile; + if (setrlimit(RLIMIT_NOFILE, &limit) != 0) + printf_console("setrlimit() failed with errno=%d\n", errno); +} +#endif diff --git a/Runtime/Utilities/File.h b/Runtime/Utilities/File.h new file mode 100644 index 0000000..b033278 --- /dev/null +++ b/Runtime/Utilities/File.h @@ -0,0 +1,194 @@ +#pragma once + +#include "Runtime/Misc/Allocator.h" +#if UNITY_OSX || UNITY_IPHONE || UNITY_ANDROID || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN +#include "fcntl.h" +#endif +#include "Runtime/Utilities/NonCopyable.h" +#include "Runtime/Utilities/PathNameUtility.h" +#include <set> +#include "Runtime/Utilities/EnumFlags.h" + +#if UNITY_WII +#include <rvlaux/file-io.h> +#endif + +#if UNITY_WINRT +// Enable only one +#define ENABLE_CRT_IO 0 +#define ENABLE_WIN_IO 1 +#endif + +class File : public NonCopyable +{ + int m_Position; + std::string m_Path; +#if UNITY_WII + bool m_Open; + WiiFileInfo m_File; +#elif UNITY_WINRT + //Microsoft::WRL::ComPtr<IInspectable> m_Stream; +# if ENABLE_CRT_IO + FILE* m_File; +# elif ENABLE_WIN_IO + HANDLE m_FileHandle; +# else + std::vector<unsigned char> m_Data; +# endif +#elif UNITY_WIN || UNITY_XENON + HANDLE m_FileHandle; +#elif UNITY_PS3 + int m_FileDescriptor; +#else + FILE* m_File; +#endif + +public: + + File (); + ~File (); + + enum Permission { kReadPermission = 0, kWritePermission = 1, kReadWritePermission = 2, kAppendPermission = 3 }; + enum AutoBehavior { kNormalBehavior = 0, kSilentReturnOnOpenFail = 1<<0, kRetryOnOpenFail = 1<<1}; + + bool Open (const std::string& path, Permission perm, AutoBehavior behavior = kNormalBehavior); + bool Close (); + + int Read (void* buffer, int size); + int Read (int position, void* buffer, int size); + + bool Write (const void* buffer, int size); + bool Write (int pos, const void* buffer, int size); + bool SetFileLength (int size); + int GetFileLength (); + int GetPosition () const { return m_Position; } + + static void SetCurrentDirectory (const std::string& path); + static const std::string& GetCurrentDirectory (); + static void CleanupClass(); + +#if UNITY_OSX || UNITY_IPHONE || UNITY_ANDROID || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN + enum LockMode { + kNone = LOCK_UN, + kShared = LOCK_SH, + kExclusive = LOCK_EX + }; + bool Lock(File::LockMode mode, bool block); +#endif +}; + +ENUM_FLAGS(File::AutoBehavior); + +/// Returns the file length of the file at pathName. If the file doesn't exist returns -1. +int GetFileLength (const std::string& pathName); +size_t GetFileSize (FILE* file); + + +std::string PathToAbsolutePath (const std::string& path); +// Path is absolute in platform dependent way! +bool IsAbsoluteFilePath( const std::string& path ); + +/// Reads the contents of the file at pathName into data. +/// byteStart is the first byte read in the file, byteLength is the number of bytes that should be read +bool ReadFromFile (const std::string& pathName, void *data, int fileStart, int byteLength); + +bool WriteBytesToFile (const void *data, int byteLength, const std::string& pathName); + +#if UNITY_WII +typedef std::basic_string<char, std::char_traits<char>, STL_ALLOCATOR_ALIGNED(kMemIOTemp, char, 32)> InputString; +#else +typedef TEMP_STRING InputString; +#endif +bool ReadStringFromFile (InputString* outData, const std::string& pathName); + +bool IsFileCreated (const std::string& path); +bool IsFileOrDirectoryInUse( const std::string& path ); +bool CreateDirectory (const std::string& pathName); + +/// Returns a set of files and folders that are children of the folder at pathname. +/// Ignores invisible files +bool GetFolderContentsAtPath (const std::string& pathName, std::set<std::string>& paths); + +/// Deletes a single file. Returns true if the file could be deleted. +/// Returns false if the file couldnt be deleted or the file was a folder. +bool DeleteFile (const std::string& file); +bool DeleteFileOrDirectory (const std::string& path); + +bool SetFileLength (const std::string& path, int size); +#if UNITY_WIN +bool SetFileLength (const HANDLE hFile, const std::string& path, int size); +bool GetFolderContentsAtPathImpl( const std::wstring& pathNameWide, std::set<std::string>& paths, bool deep ); +#endif +bool IsDirectoryCreated (const std::string& path); + +#if UNITY_EDITOR +bool MoveReplaceFile (const std::string& fromPath, const std::string& toPath); +#if UNITY_OSX || UNITY_LINUX +std::string GetFormattedFileError (int error, const std::string& operation); +#endif +#endif + +#if !UNITY_EXTERNAL_TOOL +inline bool CreateDirectoryRecursive (const std::string& pathName) +{ + if (IsDirectoryCreated(DeleteLastPathNameComponent(pathName))) + { + if (IsDirectoryCreated (pathName)) + return true; + else if (IsFileCreated (pathName)) + return false; + else + return CreateDirectory (pathName); + } + + std::string::size_type pos = pathName.rfind ("/"); + if (pos == std::string::npos) + return false; + + std::string superPath; + superPath.assign (pathName.begin (), pathName.begin () + pos); + + if (!CreateDirectoryRecursive (superPath)) + return false; + + return CreateDirectoryRecursive (pathName); +} +#endif + +// On Vista, its search indexer can wreak havoc on files that are written then renamed. Full story here: +// http://stackoverflow.com/questions/153257/random-movefileex-failures-on-vista-access-denied-looks-like-caused-by-search-i +// but in short, whenever a file is temporary or should be excluded from search indexing, we have to set those flags. +enum FileFlags { + kFileFlagTemporary = (1<<0), // File is meant to be temporary. + kFileFlagDontIndex = (1<<1), // Exclude file from search indexing (Spotlight, Vista Search, ...) + kFileFlagHidden = (1<<2), // Set file as hidden file. + + kAllFileFlags = kFileFlagTemporary | kFileFlagDontIndex +}; + +#if UNITY_WIN +bool SetFileFlags (const std::string& path, UInt32 attributeMask, UInt32 attributeValue); + +bool isHiddenFile (const std::string& path); + +#elif UNITY_EDITOR +bool SetFileFlags (const std::string& path, UInt32 attributeMask, UInt32 attributeValue); + +bool isHiddenFile (const std::string& path); + +#else +inline bool SetFileFlags (const std::string& path, UInt32 attributeMask, UInt32 attributeValue) +{ + return true; +} + +inline bool isHiddenFile (const std::string& path) +{ + return false; +} +#endif + +#if UNITY_OSX +void SetupFileLimits (); +#endif + diff --git a/Runtime/Utilities/FileStripped.h b/Runtime/Utilities/FileStripped.h new file mode 100644 index 0000000..c6590c6 --- /dev/null +++ b/Runtime/Utilities/FileStripped.h @@ -0,0 +1,10 @@ + +#ifndef __FILE_STRIPPED__ + +#if ENABLE_PROFILER || DEBUGMODE +#define __FILE_STRIPPED__ __FILE__ +#else +#define __FILE_STRIPPED__ "" +#endif + +#endif
\ No newline at end of file diff --git a/Runtime/Utilities/FileUtilities.cpp b/Runtime/Utilities/FileUtilities.cpp new file mode 100644 index 0000000..6ae5cb0 --- /dev/null +++ b/Runtime/Utilities/FileUtilities.cpp @@ -0,0 +1,27 @@ +#include "UnityPrefix.h" +#include "Runtime/Utilities/FileUtilities.h" +#include "Runtime/Utilities/GUID.h" + + +using namespace std; + + +#if UNITY_HAVE_GUID_INIT + +string GetUniqueTempPath (const std::string& basePath) +{ + while (true) + { + UnityGUID guid; guid.Init(); + string tmpFilePath = basePath + GUIDToString(guid); + if (!IsFileCreated(tmpFilePath)) + return tmpFilePath; + } +} + +string GetUniqueTempPathInProject () +{ + return GetUniqueTempPath ("Temp/UnityTempFile-"); +} + +#endif diff --git a/Runtime/Utilities/FileUtilities.h b/Runtime/Utilities/FileUtilities.h new file mode 100644 index 0000000..c9589c2 --- /dev/null +++ b/Runtime/Utilities/FileUtilities.h @@ -0,0 +1,113 @@ +#ifndef FILEUTILITIES_H +#define FILEUTILITIES_H + +#include <list> +#include <string> +#include <set> +#include "PathNameUtility.h" +#include "DateTime.h" +#include "File.h" + +using std::string; + +#if UNITY_EDITOR + +/// Returns the real pathname of path +/// (eg. if pathname is all lowercase, the returned string will match the actual pathname) +/// If there is no file at path returns path. +std::string GetActualPathSlow (const std::string & input); +std::string GetActualAbsolutePathSlow (const std::string & path); + +// Copies a file or folder. Resolves all symlinks in from before copying. +bool CopyFileOrDirectory (const string& from, const string& to); +bool MoveToTrash (const string& path); +bool CopyReplaceFile (string from, string to); +bool MoveFileOrDirectory (const string& fromPath, const string& toPath); + +string GetProjectRelativePath (const string& path); + +bool CreateDirectory (const string& pathName); + +/// Returns a set of files and folders that are deep children of the folder at pathname. +/// Ignores invisible files +bool GetDeepFolderContentsAtPath (const string& pathName, std::set<string>& paths); + +/// Returns the content modification date for the file at path or zero timestamp if file couldn't be found +DateTime GetContentModificationDate (const string& path); + +/// Sets the content modification date (write time) for the file at path +bool SetContentModificationDateToCurrentTime (const string& path); + +bool CreateDirectorySafe (const string& pathName); + +std::string GenerateUniquePath (std::string inPath); +std::string GenerateUniquePathSafe (const std::string& pathName); +std::string GetUniqueTempPathInProject (); +std::string GetUniqueTempPath (const std::string& basePath); + +bool IsPathCreated (const std::string& path); + +#if UNITY_OSX || UNITY_LINUX +inline void UnixTimeToUnityTime (time_t time, DateTime &dateTime ) +{ + // Seconds from 1904 to 2001: 3061152000 (kCFAbsoluteTimeIntervalSince1904) + // Seconds from 1970 to 2001: 978307200 (kCFAbsoluteTimeIntervalSince1970) + // Thus seconds from 1904 to 1970: 2082844800 + UInt64 seconds = time - 2082844800; + dateTime.fraction = 0; + dateTime.lowSeconds = seconds & 0xFFFFFFFF; + dateTime.highSeconds = seconds >> 32; +} +#endif + +#if UNITY_OSX +bool PathToFSSpec (const std::string& path, FSSpec* spec); +#endif + +#if UNITY_WIN +void FileTimeToUnityTime( const FILETIME& ft, DateTime& dateTime ); +bool CopyFileOrDirectoryCheckPermissions (const string& from, const string& to, bool &accessdenied); +#endif + +enum AtomicWriteMode { + kProjectTempFolder = 0, + #if UNITY_OSX + kSystemTempFolder = 1, + #endif + kNotAtomic = 2, +}; +bool WriteStringToFile (const string& inData, const string& pathName, AtomicWriteMode mode, UInt32 fileFlags); + +bool IsSymlink (const std::string& pathName); + +// Resolve symlinks & canonicalize the path +std::string ResolveSymlinks (const std::string& pathName); + +std::string GetUserAppDataFolder (); +std::string GetUserAppCacheFolder (); + +#endif // UNITY_EDITOR + +string GetApplicationPath (); +string GetApplicationContentsPath (); +string GetApplicationFolder (); +string GetApplicationLocalFolder(); +string GetApplicationManagedPath(); +string GetApplicationTemporaryFolder(); + +#if UNITY_STANDALONE || UNITY_EDITOR +string GetApplicationNativeLibsPath(); +#endif + +bool CreateFile (const string& path, int creator = '?', int fileType = '?'); + +#if UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN +void SetApplicationPath( std::string path ); +bool IsSymlink(const std::string &pathName); +std::string ResolveSymlink(const std::string& pathName); +#if !UNITY_BB10 && !UNITY_TIZEN +std::string GetUserConfigFolder (); +#endif +#endif + +#endif diff --git a/Runtime/Utilities/GLSLUtilities.cpp b/Runtime/Utilities/GLSLUtilities.cpp new file mode 100644 index 0000000..2152ef3 --- /dev/null +++ b/Runtime/Utilities/GLSLUtilities.cpp @@ -0,0 +1,835 @@ +#include "UnityPrefix.h" +#include "GLSLUtilities.h" +#include "External/shaderlab/Library/ShaderLabErrors.h" + +#if UNITY_ANDROID || UNITY_IPHONE + #include "Runtime/Shaders/GraphicsCaps.h" +#endif + +using namespace std; + +// -------------------------------------------------------------------------- + + +void OutputGLSLShaderError(const char* log, GLSLErrorType errorType, ShaderErrors& outErrors) +{ + + const char* strErrorIds[kErrorCount] = + { + "GLSL Error in Vertex Shader: ", + "GLSL Error in Fragment Shader: ", + "GLSL Error while linking: " + }; + + string errorMessage = string(strErrorIds[errorType]) + log; + + int errorLine = 0; + switch(errorType) + { + case kErrorCompileVertexShader: + case kErrorCompileFragShader: + { + size_t b = errorMessage.find('('); + size_t e = errorMessage.find(')'); + if(b != string::npos && e != string::npos && e>b) + { + string errorLineText = errorMessage.substr(b + 1, e - b - 1); + errorLine = StringToInt(errorLineText); + } + + outErrors.AddShaderError (errorMessage, errorLine, false); + } + break; + case kErrorLinkProgram: + errorMessage += log; + outErrors.AddShaderError (errorMessage, errorLine, false); + break; + default: + break; + } +} + + +// -------------------------------------------------------------------------- +// GLSL patching for fog + +#define DEBUG_FOG_PATCHING 0 + +static inline bool IsNewline( char c ) { return c == '\n' || c == '\r'; } + +static size_t FindStartOfBlock (const std::string& src, const std::string& token) +{ + size_t pos = src.find (token); + if (pos == std::string::npos) + return pos; + const size_t n = src.size(); + while (pos < n && !IsNewline(src[pos])) ++pos; // skip until newline + while (pos < n && IsNewline(src[pos])) ++pos; // skip newlines + if (pos >= n) + return std::string::npos; + return pos; +} + +static bool SkipUntilStatementEnd (const std::string& src, size_t& pos) +{ + const size_t n = src.size(); + while (pos < n && src[pos] != ';') ++pos; if (pos < n) ++pos; + while (pos < n && IsNewline(src[pos])) ++pos; // skip following newlines + return pos < n; +} + +static void SkipUntilStatementBegin (const std::string& src, size_t& pos) +{ + const size_t n = src.size(); + while (pos > 0 && src[pos] != ';' && src[pos] != '{') --pos; if (pos < n) ++pos; + while (pos < n && IsSpace(src[pos])) ++pos; // skip following whitespace +} + +static size_t SkipGLSLDirectives (const std::string& src, size_t startPos = 0) +{ + size_t pos = startPos; + const size_t n = src.size(); + while (pos < n) + { + // skip whitespace + while (pos < n && IsSpace(src[pos])) ++pos; + // have a '#'? + if (pos >= n || src[pos] != '#') + break; + // skip until end of line + while (pos < n && !IsNewline(src[pos])) ++pos; + } + return pos; +} + + +static int ParseGLSLVersion (const std::string& src, size_t startPos = 0) +{ + size_t pos = startPos; + const size_t n = src.size(); + // skip whitespace + while (pos < n && IsSpace(src[pos])) ++pos; + // have a '#'? + if (pos >= n || src[pos] != '#') + return 0; + ++pos; + // skip whitespace + while (pos < n && IsSpace(src[pos])) ++pos; + // have a "version"? + if (0 != strncmp(src.c_str() + pos, "version", 7)) + return 0; + pos += 7; + // skip whitespace + while (pos < n && IsSpace(src[pos])) ++pos; + // parse a number + int v = atoi(src.c_str() + pos); + return v; +} + +static const char* GetGLSLFogData (int version, bool vertex) +{ + if (version >= 150) + { + if (vertex) + return "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\nout float _unity_FogVar;\n"; + else + return "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\nin float _unity_FogVar;\n"; + } + return "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\nvarying float _unity_FogVar;\n"; +} + +static bool InsertFogVertexCode (std::string& src) +{ + size_t vertexStart = FindStartOfBlock (src, "#ifdef VERTEX"); + if (vertexStart == std::string::npos) + return false; + const int version = ParseGLSLVersion (src, vertexStart); + vertexStart = SkipGLSLDirectives (src, vertexStart); + + const char* fogData = GetGLSLFogData(version, true); + src.insert (vertexStart, fogData); + + size_t posWriteStart = src.find ("gl_Position", vertexStart); + if (posWriteStart == std::string::npos) + return false; + + size_t pos = posWriteStart; + if (!SkipUntilStatementEnd (src, pos)) + return false; + + // insert fog code at pos! + src.insert (pos, "_unity_FogVar = gl_Position.z;\n"); + + return true; +} + +static bool InsertFogFragmentCode (std::string& src, FogMode fog) +{ + size_t fragmentStart = FindStartOfBlock (src, "#ifdef FRAGMENT"); + if (fragmentStart == std::string::npos) + return false; + const int version = ParseGLSLVersion (src, fragmentStart); + fragmentStart = SkipGLSLDirectives (src, fragmentStart); + + const char* fogData = GetGLSLFogData(version, false); + src.insert (fragmentStart, fogData); + + bool writesToData = true; // writes to gl_FragData vs. gl_FragColor + size_t colorWriteStart = src.find ("gl_FragData[0]", fragmentStart); + if (colorWriteStart == std::string::npos) + { + colorWriteStart = src.find ("gl_FragColor", fragmentStart); + if (colorWriteStart == std::string::npos) + return false; + writesToData = false; + } + + size_t pos = colorWriteStart; + if (!SkipUntilStatementEnd (src, pos)) + return false; + + // insert fog code at pos! + std::string color = writesToData ? "gl_FragData[0]" : "gl_FragColor"; + if (fog == kFogExp2) + { + // fog = exp(-(density*z)^2) + src.insert (pos, + " float _patchFog = _unity_FogParams.x * _unity_FogVar;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " " + color + ".rgb = mix (_unity_FogColor.rgb, " + color + ".rgb, _patchFog);\n"); + } + else if (fog == kFogExp) + { + // fog = exp(-density*z) + src.insert (pos, + " float _patchFog = _unity_FogParams.y * _unity_FogVar;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " " + color + ".rgb = mix (_unity_FogColor.rgb, " + color + ".rgb, _patchFog);\n"); + } + else if (fog == kFogLinear) + { + // fog = (end-z)/(end-start) + src.insert (pos, + " float _patchFog = clamp (_unity_FogParams.z * _unity_FogVar + _unity_FogParams.w, 0.0, 1.0);\n" + " " + color + ".rgb = mix (_unity_FogColor.rgb, " + color + ".rgb, _patchFog);\n"); + } + + return true; +} + +bool PatchShaderFogGLSL (std::string& src, FogMode fog) +{ + #if DEBUG_FOG_PATCHING + printf_console ("GLSL fog patching: original shader:\n%s\n", src.c_str()); + #endif + + if (!InsertFogVertexCode (src)) + return false; + if (!InsertFogFragmentCode (src, fog)) + return false; + + #if DEBUG_FOG_PATCHING + printf_console ("GLSL fog patching: after patching, fog mode %d:\n%s\n", fog, src.c_str()); + #endif + return true; +} + + + +static bool InsertFogVertexCodeGLES (std::string& src, FogMode fog, bool canUseOptimizedCode) +{ + size_t posWriteStart = src.find ("gl_Position"); + if (posWriteStart == std::string::npos) + return false; + + size_t pos = posWriteStart; + if (!SkipUntilStatementEnd (src, pos)) + return false; + + // TODO: remove duplication between optimized/normal fog code + + // insert fog code at pos! + if (fog == kFogExp2) + { + // fog = exp(-(density*z)^2) + if(canUseOptimizedCode) + { + src.insert (pos, + " float _patchFog = _unity_FogParams.x * gl_Position.z;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _unity_FogVar = vec4(clamp (exp2(-_patchFog), 0.0, 1.0)); _unity_FogVar.a = 1.0;\n" + " _unity_FogColorPreMul = _unity_FogColor * (vec4(1.0)-_unity_FogVar);\n" + ); + } + else + { + src.insert (pos, + " float _patchFog = _unity_FogParams.x * gl_Position.z;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _unity_FogVar = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + ); + } + } + else if (fog == kFogExp) + { + // fog = exp(-density*z) + if(canUseOptimizedCode) + { + src.insert (pos, + " float _patchFog = _unity_FogParams.y * gl_Position.z;\n" + " _unity_FogVar = vec4(clamp (exp2(-_patchFog), 0.0, 1.0)); _unity_FogVar.a = 1.0;\n" + " _unity_FogColorPreMul = _unity_FogColor * (vec4(1.0)-_unity_FogVar);\n" + ); + } + else + { + src.insert (pos, + " float _patchFog = _unity_FogParams.y * gl_Position.z;\n" + " _unity_FogVar = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + ); + } + } + else if (fog == kFogLinear) + { + // fog = (end-z)/(end-start) + if(canUseOptimizedCode) + { + src.insert (pos, + " _unity_FogVar = vec4(clamp (_unity_FogParams.z * gl_Position.z + _unity_FogParams.w, 0.0, 1.0)); _unity_FogVar.a = 1.0;\n" + " _unity_FogColorPreMul = _unity_FogColor * (vec4(1.0)-_unity_FogVar);\n" + ); + } + else + { + src.insert (pos, + " _unity_FogVar = clamp (_unity_FogParams.z * gl_Position.z + _unity_FogParams.w, 0.0, 1.0);\n" + ); + } + } + + return true; +} + +static bool InsertFogFragmentCodeGLES (std::string& src, bool canUseOptimizedCode) +{ + bool writesToData = true; // writes to gl_FragData vs. gl_FragColor + size_t colorWriteStart = src.find ("gl_FragData[0]"); + if (colorWriteStart == std::string::npos) + { + colorWriteStart = src.find ("gl_FragColor"); + if (colorWriteStart == std::string::npos) + return false; + writesToData = false; + } + + // iPad2 crashes with some shaders using fog (not all of them though), case 399638. + // Seems to avoid the crash if we don't read gl_FragColor after writing it. So instead, + // introduce a new local variable, replace any previous uses with it, do fog patching + // on it, and write out to final destination in the end. + + // replace color with new variable name + const std::string color = writesToData ? "gl_FragData[0]" : "gl_FragColor"; + replace_string (src, color, "_patchFragColor"); + + // find color output statement start & end + size_t posBegin, posEnd; + posBegin = posEnd = colorWriteStart; + if (!SkipUntilStatementEnd (src, posEnd)) + return false; + SkipUntilStatementBegin (src, posBegin); + + // insert new output variable declaration + src.insert (posBegin, "lowp vec4 _patchFragColor;\n"); + posEnd += strlen("lowp vec4 _patchFragColor;\n"); + + // insert fog code + if(canUseOptimizedCode) + src.insert (posEnd, " _patchFragColor = _patchFragColor * _unity_FogVar + _unity_FogColorPreMul; " + color + " = _patchFragColor;\n"); + else + src.insert (posEnd, " _patchFragColor.rgb = mix (_unity_FogColor.rgb, _patchFragColor.rgb, _unity_FogVar); " + color + " = _patchFragColor;\n"); + + return true; +} + +bool PatchShaderFogGLES (std::string& srcVS, std::string& srcPS, FogMode fog, bool useOptimizedFogCode) +{ + #if DEBUG_FOG_PATCHING + printf_console ("GLES fog patching: original vertex shader:\n%s\n...and pixel shader:\n%s\n", srcVS.c_str(), srcPS.c_str()); + #endif + + const int versionVS = ParseGLSLVersion (srcVS); + const int versionPS = ParseGLSLVersion (srcPS); + const size_t startVS = SkipGLSLDirectives(srcVS); + const size_t startPS = SkipGLSLDirectives(srcPS); + const char* fogDataVS = NULL; + const char* fogDataPS = NULL; + if (useOptimizedFogCode) + { + if (versionVS >= 150) + { + fogDataVS = "uniform highp vec4 _unity_FogParams;\nuniform lowp vec4 _unity_FogColor;\nout lowp vec4 _unity_FogColorPreMul;\nout lowp vec4 _unity_FogVar;\n"; + fogDataPS = "in lowp vec4 _unity_FogColorPreMul;\nin lowp vec4 _unity_FogVar;\n"; + } + else + { + fogDataVS = "uniform highp vec4 _unity_FogParams;\nuniform lowp vec4 _unity_FogColor;\nvarying lowp vec4 _unity_FogColorPreMul;\nvarying lowp vec4 _unity_FogVar;\n"; + fogDataPS = "varying lowp vec4 _unity_FogColorPreMul;\nvarying lowp vec4 _unity_FogVar;\n"; + } + } + else + { + if (versionPS >= 150) + { + fogDataVS = "uniform highp vec4 _unity_FogParams;\nout lowp float _unity_FogVar;\n"; + fogDataPS = "uniform lowp vec4 _unity_FogColor;\nin lowp float _unity_FogVar;\n"; + } + else + { + fogDataVS = "uniform highp vec4 _unity_FogParams;\nvarying lowp float _unity_FogVar;\n"; + fogDataPS = "uniform lowp vec4 _unity_FogColor;\nvarying lowp float _unity_FogVar;\n"; + } + } + srcVS.insert (startVS, fogDataVS); + srcPS.insert (startPS, fogDataPS); + + + if (!InsertFogVertexCodeGLES (srcVS, fog, useOptimizedFogCode)) + return false; + if (!InsertFogFragmentCodeGLES (srcPS, useOptimizedFogCode)) + return false; + + #if DEBUG_FOG_PATCHING + printf_console ("GLES fog patching: after patching, fog mode %d:\n%s\n...and pixel shader:\n%s\n", fog, srcVS.c_str(), srcPS.c_str()); + #endif + return true; +} + +static unsigned CalculateVaryingCount(const std::string& src) +{ + unsigned varCount = 0; + + size_t varUsage = src.find("varying "); + while(varUsage != std::string::npos) + { + ++varCount; + varUsage = src.find("varying ", varUsage+7); + } + + return varCount; +} + +bool CanUseOptimizedFogCodeGLES(const std::string& srcVS) +{ +#if UNITY_ANDROID || UNITY_IPHONE + // we add 2 varyings in optimized path + // in normal path we add one, so would be good to check it too, but we dont take "packing" into account + // so we just act conservatively and apply optimization only when we are sure it works + return (CalculateVaryingCount(srcVS) + 2 <= gGraphicsCaps.gles20.maxVaryings); +#else + (void)srcVS; + return true; +#endif +} + +bool IsShaderParameterArray(const char* name, unsigned nameLen, int arraySize, bool* isZeroElem) +{ + bool zeroElem = nameLen > 3 && strcmp (name+nameLen-3, "[0]") == 0; + if(isZeroElem) + *isZeroElem = zeroElem; + + return arraySize > 1 || zeroElem; +} + + +// -------------------------------------------------------------------------- + +#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" + +SUITE (GlslFogPatchingTests) +{ + struct GlslPatchTestFixture + { + std::string vs, fs, s; + std::string expvs, expfs, exps; + bool DoPatching(FogMode mode, bool checkOptimizedGLESCode) + { + s = "#ifdef VERTEX\n" + vs + "\n#endif\n#ifdef FRAGMENT\n" + fs + "\n#endif\n"; + bool ok = true; + ok &= PatchShaderFogGLSL (s, mode); + ok &= PatchShaderFogGLES (vs, fs, mode, checkOptimizedGLESCode); + return ok; + } + }; + + TEST(SkipGLSLDirectivesWorks) + { + const char* s; + CHECK_EQUAL(0, SkipGLSLDirectives("void")); // no directives + CHECK_EQUAL(2, SkipGLSLDirectives(" void")); // skipped space + CHECK_EQUAL(2, SkipGLSLDirectives("\n\nvoid")); // skipped newlines + CHECK_EQUAL(12, SkipGLSLDirectives("#pragma foo\n")); // skipped whole line + CHECK_EQUAL(14, SkipGLSLDirectives(" #pragma foo\n")); // skipped whole line + CHECK_EQUAL(15, SkipGLSLDirectives("#pragma foo\r\n\r\n")); // skipped whole line, including various newlines + CHECK_EQUAL(24, SkipGLSLDirectives("#pragma foo\n#pragma foo\nvoid")); // skipped two lines + } + + TEST(ParseGLSLVersionWorks) + { + CHECK_EQUAL(0, ParseGLSLVersion("")); + CHECK_EQUAL(0, ParseGLSLVersion("void")); + CHECK_EQUAL(0, ParseGLSLVersion(" # pragma")); + CHECK_EQUAL(0, ParseGLSLVersion("#version")); + + CHECK_EQUAL(120, ParseGLSLVersion("#version 120")); + CHECK_EQUAL(200, ParseGLSLVersion(" # version 200")); + } + + TEST_FIXTURE(GlslPatchTestFixture,PatchSkipsVersionAndExtensionDirectives) + { + vs = + "#version 120\n" + "#extension bar : enable\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "}"; + fs = + "#extension bar : enable\n" + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + "}"; + CHECK(DoPatching(kFogExp2,true)); + expvs = + "#version 120\n" + "#extension bar : enable\n" + "uniform highp vec4 _unity_FogParams;\n"; + expfs = + "#extension bar : enable\n" + "varying lowp vec4 _unity_FogColorPreMul;\n"; + exps = + "#ifdef VERTEX\n" + "#version 120\n" + "#extension bar : enable\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "_unity_FogVar = gl_Position.z;\n" + "}\n" + "#endif\n" + "#ifdef FRAGMENT\n" + "#extension bar : enable\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.x * _unity_FogVar;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " gl_FragColor.rgb = mix (_unity_FogColor.rgb, gl_FragColor.rgb, _patchFog);\n" + "}\n" + "#endif\n"; + CHECK (BeginsWith(vs, expvs)); + CHECK (BeginsWith(fs, expfs)); + CHECK_EQUAL (exps, s); + } + + TEST_FIXTURE(GlslPatchTestFixture,PatchTakesVersionIntoAccount) + { + vs = + "#version 300 es\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "}"; + fs = + " #version 420\n" + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + "}"; + CHECK(DoPatching(kFogExp2,true)); + expvs = + "#version 300 es\n" + "uniform highp vec4 _unity_FogParams;\n" + "uniform lowp vec4 _unity_FogColor;\n" + "out lowp vec4 _unity_FogColorPreMul;\n" + "out lowp vec4 _unity_FogVar;\n"; + expfs = + " #version 420\n" + "in lowp vec4 _unity_FogColorPreMul;\n" + "in lowp vec4 _unity_FogVar;\n"; + exps = + "#ifdef VERTEX\n" + "#version 300 es\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "out float _unity_FogVar;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "_unity_FogVar = gl_Position.z;\n" + "}\n" + "#endif\n" + "#ifdef FRAGMENT\n" + " #version 420\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "in float _unity_FogVar;\n" + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.x * _unity_FogVar;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " gl_FragColor.rgb = mix (_unity_FogColor.rgb, gl_FragColor.rgb, _patchFog);\n" + "}\n" + "#endif\n"; + CHECK (BeginsWith(vs, expvs)); + CHECK (BeginsWith(fs, expfs)); + CHECK_EQUAL (exps, s); + } + + TEST_FIXTURE(GlslPatchTestFixture,PatchExp2WriteToColorOptimized) + { + vs = + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "}"; + fs = + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + "}"; + CHECK(DoPatching(kFogExp2,true)); + expvs = + "uniform highp vec4 _unity_FogParams;\n" + "uniform lowp vec4 _unity_FogColor;\n" + "varying lowp vec4 _unity_FogColorPreMul;\n" + "varying lowp vec4 _unity_FogVar;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.x * gl_Position.z;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _unity_FogVar = vec4(clamp (exp2(-_patchFog), 0.0, 1.0)); _unity_FogVar.a = 1.0;\n" + " _unity_FogColorPreMul = _unity_FogColor * (vec4(1.0)-_unity_FogVar);\n" + "}"; + expfs = + "varying lowp vec4 _unity_FogColorPreMul;\n" + "varying lowp vec4 _unity_FogVar;\n" + "void main() {\n" + " lowp vec4 _patchFragColor;\n" + "_patchFragColor = vec4(1,2,3,4);\n" + " _patchFragColor = _patchFragColor * _unity_FogVar + _unity_FogColorPreMul; gl_FragColor = _patchFragColor;\n" + "}"; + exps = + "#ifdef VERTEX\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "_unity_FogVar = gl_Position.z;\n" + "}\n" + "#endif\n" + "#ifdef FRAGMENT\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.x * _unity_FogVar;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " gl_FragColor.rgb = mix (_unity_FogColor.rgb, gl_FragColor.rgb, _patchFog);\n" + "}\n" + "#endif\n"; + CHECK_EQUAL (expvs, vs); + CHECK_EQUAL (expfs, fs); + CHECK_EQUAL (exps, s); + } + + TEST_FIXTURE(GlslPatchTestFixture,PatchExp2WriteToColor) + { + vs = + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "}"; + fs = + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + "}"; + CHECK(DoPatching(kFogExp2,false)); + expvs = + "uniform highp vec4 _unity_FogParams;\n" + "varying lowp float _unity_FogVar;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.x * gl_Position.z;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _unity_FogVar = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + "}"; + expfs = + "uniform lowp vec4 _unity_FogColor;\n" + "varying lowp float _unity_FogVar;\n" + "void main() {\n" + " lowp vec4 _patchFragColor;\n" + "_patchFragColor = vec4(1,2,3,4);\n" + " _patchFragColor.rgb = mix (_unity_FogColor.rgb, _patchFragColor.rgb, _unity_FogVar); gl_FragColor = _patchFragColor;\n" + "}"; + exps = + "#ifdef VERTEX\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "_unity_FogVar = gl_Position.z;\n" + "}\n" + "#endif\n" + "#ifdef FRAGMENT\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "void main() {\n" + " gl_FragColor = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.x * _unity_FogVar;\n" + " _patchFog = _patchFog * _patchFog;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " gl_FragColor.rgb = mix (_unity_FogColor.rgb, gl_FragColor.rgb, _patchFog);\n" + "}\n" + "#endif\n"; + CHECK_EQUAL (expvs, vs); + CHECK_EQUAL (expfs, fs); + CHECK_EQUAL (exps, s); + } + + TEST_FIXTURE(GlslPatchTestFixture,PatchExpWriteToDataWithStuffAroundOptimized) + { + vs = + "varying float foo;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + " foo = 1.0;\n" + "}"; + fs = + "varying float foo;\n" + "void main() {\n" + " foo = 2.0;\n" + " gl_FragData[0] = vec4(1,2,3,4);\n" + "}"; + CHECK(DoPatching(kFogExp,true)); + expvs = + "uniform highp vec4 _unity_FogParams;\n" + "uniform lowp vec4 _unity_FogColor;\n" + "varying lowp vec4 _unity_FogColorPreMul;\n" + "varying lowp vec4 _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.y * gl_Position.z;\n" + " _unity_FogVar = vec4(clamp (exp2(-_patchFog), 0.0, 1.0)); _unity_FogVar.a = 1.0;\n" + " _unity_FogColorPreMul = _unity_FogColor * (vec4(1.0)-_unity_FogVar);\n" + " foo = 1.0;\n" + "}"; + expfs = + "varying lowp vec4 _unity_FogColorPreMul;\n" + "varying lowp vec4 _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " foo = 2.0;\n" + " lowp vec4 _patchFragColor;\n" + "_patchFragColor = vec4(1,2,3,4);\n" + " _patchFragColor = _patchFragColor * _unity_FogVar + _unity_FogColorPreMul; gl_FragData[0] = _patchFragColor;\n" + "}"; + exps = + "#ifdef VERTEX\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "_unity_FogVar = gl_Position.z;\n" + " foo = 1.0;\n" + "}\n" + "#endif\n" + "#ifdef FRAGMENT\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " foo = 2.0;\n" + " gl_FragData[0] = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.y * _unity_FogVar;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " gl_FragData[0].rgb = mix (_unity_FogColor.rgb, gl_FragData[0].rgb, _patchFog);\n" + "}\n" + "#endif\n"; + CHECK_EQUAL (expvs, vs); + CHECK_EQUAL (expfs, fs); + CHECK_EQUAL (exps, s); + } + + TEST_FIXTURE(GlslPatchTestFixture,PatchExpWriteToDataWithStuffAround) + { + vs = + "varying float foo;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + " foo = 1.0;\n" + "}"; + fs = + "varying float foo;\n" + "void main() {\n" + " foo = 2.0;\n" + " gl_FragData[0] = vec4(1,2,3,4);\n" + "}"; + CHECK(DoPatching(kFogExp,false)); + expvs = + "uniform highp vec4 _unity_FogParams;\n" + "varying lowp float _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.y * gl_Position.z;\n" + " _unity_FogVar = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " foo = 1.0;\n" + "}"; + expfs = + "uniform lowp vec4 _unity_FogColor;\n" + "varying lowp float _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " foo = 2.0;\n" + " lowp vec4 _patchFragColor;\n" + "_patchFragColor = vec4(1,2,3,4);\n" + " _patchFragColor.rgb = mix (_unity_FogColor.rgb, _patchFragColor.rgb, _unity_FogVar); gl_FragData[0] = _patchFragColor;\n" + "}"; + exps = + "#ifdef VERTEX\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " gl_Position = vec4(1,2,3,4);\n" + "_unity_FogVar = gl_Position.z;\n" + " foo = 1.0;\n" + "}\n" + "#endif\n" + "#ifdef FRAGMENT\n" + "uniform vec4 _unity_FogColor; uniform vec4 _unity_FogParams;\n" + "varying float _unity_FogVar;\n" + "varying float foo;\n" + "void main() {\n" + " foo = 2.0;\n" + " gl_FragData[0] = vec4(1,2,3,4);\n" + " float _patchFog = _unity_FogParams.y * _unity_FogVar;\n" + " _patchFog = clamp (exp2(-_patchFog), 0.0, 1.0);\n" + " gl_FragData[0].rgb = mix (_unity_FogColor.rgb, gl_FragData[0].rgb, _patchFog);\n" + "}\n" + "#endif\n"; + CHECK_EQUAL (expvs, vs); + CHECK_EQUAL (expfs, fs); + CHECK_EQUAL (exps, s); + } + + TEST(CalculateVaryingCount) + { + // TODO: should we handle packing? for now we dont need it, but point for the future + std::string test = "varying float foo; varying float foo2;"; + CHECK_EQUAL(CalculateVaryingCount(test), 2); + } +} // SUITE + +#endif // ENABLE_UNIT_TESTS diff --git a/Runtime/Utilities/GLSLUtilities.h b/Runtime/Utilities/GLSLUtilities.h new file mode 100644 index 0000000..147339d --- /dev/null +++ b/Runtime/Utilities/GLSLUtilities.h @@ -0,0 +1,30 @@ +#ifndef GLSLUTILITIES_H +#define GLSLUTILITIES_H + +#include "Runtime/GfxDevice/GfxDeviceTypes.h" + +class ShaderErrors; + +enum GLSLErrorType +{ + kErrorCompileVertexShader, + kErrorCompileFragShader, + kErrorLinkProgram, + kErrorCount +}; +void OutputGLSLShaderError (const char* log, GLSLErrorType errorType, ShaderErrors& outErrors); + +bool CanUseOptimizedFogCodeGLES(const std::string& srcVS); + +bool PatchShaderFogGLSL (std::string& src, FogMode fog); +bool PatchShaderFogGLES (std::string& srcVS, std::string& srcPS, FogMode fog, bool useOptimizedFogCode); + +// TODO: might be better to share whole parm filling procedure, but good enough for now +// TODO: as for now all gles impls we know returns first elem name +// in case of using array[0] only - arraySize would be 1 +// in case of using array[1] only - arraySize would be 2 and uniform name would be array[0] +// but this behaviour might change in future +bool IsShaderParameterArray(const char* name, unsigned nameLen, int arraySize, bool* isZeroElem=0); + + +#endif diff --git a/Runtime/Utilities/GUID.cpp b/Runtime/Utilities/GUID.cpp new file mode 100644 index 0000000..9fe4f7f --- /dev/null +++ b/Runtime/Utilities/GUID.cpp @@ -0,0 +1,241 @@ +#include "UnityPrefix.h" +#include "GUID.h" +#include "LogAssert.h" +#include "StaticAssert.h" +#include <string> + +#if UNITY_IPHONE +#include <CoreFoundation/CFUUID.h> +#endif + +#if UNITY_WIN +#include <ObjBase.h> +#endif + +#if UNITY_XENON +#include <comdecl.h> +#include <time.h> +#endif + +using namespace std; + +#if UNITY_ANDROID +#include <fcntl.h> +#include <unistd.h> +#elif UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN +#include <fcntl.h> +#include <sys/time.h> +#endif + +#if UNITY_PS3 +#include <sys/random_number.h> +#endif + +#if UNITY_HAVE_GUID_INIT +void UnityGUID::Init () +{ +#if UNITY_OSX || UNITY_IPHONE + CompileTimeAssert (sizeof (CFUUIDBytes) == 16, "GIUD shoud be 16 bytes"); +#elif UNITY_WIN || UNITY_XENON + CompileTimeAssert (sizeof (GUID) == 16, "GIUD shoud be 16 bytes"); +#endif + + +#if UNITY_OSX || UNITY_IPHONE + + CFUUIDRef myUUID; + myUUID = CFUUIDCreate (kCFAllocatorDefault); + CFUUIDBytes bytes = CFUUIDGetUUIDBytes (myUUID); + memcpy (data, &bytes, sizeof (CFUUIDBytes)); + CFRelease (myUUID); + +#elif UNITY_ANDROID || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN + + #pragma message("Android GUID/UUID creation NOT tested!") + { + unsigned char bytes[16]; + + struct timeval tv ; + gettimeofday(&tv, 0) ; + int fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + fd = open("/dev/random", O_RDONLY | O_NONBLOCK) ; + + srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec) ; + + // Crank the random number generator a few times + gettimeofday(&tv, 0) ; + for (int i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--) rand() ; + + int n = 16 ; + int lose_counter = 0 ; + unsigned char *cp = (unsigned char *) bytes ; + while (n > 0 && fd != -1) + { + int i = read(fd, cp, n) ; + if (i <= 0) + { + if (lose_counter++ > 16) break ; + continue ; + } + n -= i ; + cp += i ; + lose_counter = 0 ; + } + + if (fd != -1) + close(fd); + + // We do this all the time, but this is the only source of + // randomness if /dev/random/urandom is out to lunch. + for (unsigned int i = 0; i<16; ++i) + bytes[i] ^= (rand() >> 7) & 0xFF ; + + // clock_seq is bytes #8 and #9 + uint16_t clock_seq = (uint16_t(bytes[8]) << 8) + bytes[9] ; + clock_seq = (clock_seq & 0x3FFF) | 0x8000 ; + bytes[8] = clock_seq >> 8 ; + bytes[9] = clock_seq & 0xFF ; + + // time_hi_and_version is bytes #6 and #7 + uint16_t time_hi_and_version = (uint16_t(bytes[6]) << 8) + bytes[7] ; + time_hi_and_version = (time_hi_and_version & 0x0FFF) | 0x4000 ; + bytes[6] = time_hi_and_version >> 8 ; + bytes[7] = time_hi_and_version & 0xFF ; + + memcpy (data, &bytes, sizeof (data)); + } +#elif UNITY_WIN + + GUID guid; + ::CoCreateGuid( &guid ); + memcpy( data, &guid, sizeof(guid) ); + +#elif UNITY_PS3 + + sys_get_random_number( &data, sizeof(UnityGUID) ); + +#elif UNITY_XENON + + // RFC 4122, section 4.4 (Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers) + // http://www.ietf.org/rfc/rfc4122.txt + GUID guid; + unsigned char* bytes = (unsigned char*)&guid; + + srand( time(NULL) ); + for (unsigned int i = 0; i<16; ++i) + bytes[i] ^= (rand() >> 7) & 0xFF; + + // V4 + guid.Data3 &= 0x0fff; + guid.Data3 |= (4 << 12); + guid.Data4[0] &= 0x3f; + guid.Data4[0] |= 0x80; + + memcpy( data, &guid, sizeof(guid) ); + +#else + + ErrorString("GUID::Init() not implemented."); + +#endif +} +#endif + +bool CompareGUIDStringLess (const UnityGUID& lhsGUID, const UnityGUID& rhsGUID) +{ + char lhs[32]; + char rhs[32]; + GUIDToString(lhsGUID, lhs); + GUIDToString(rhsGUID, rhs); + + for (int i=0;i<32;i++) + { + if (lhs[i] != rhs[i]) + return lhs[i] < rhs[i]; + } + + return false; +} + +const char kHexToLiteral[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +string GUIDToString(const UnityGUID& guid) +{ + char name[kGUIDStringLength+1]; + GUIDToString (guid, name); + name[kGUIDStringLength] = '\0'; + return name; +} + +void GUIDToString(const UnityGUID& guid, char* name) +{ + for (int i=0;i<4;i++) + { + for (int j=8;j--;) + { + UInt32 cur = guid.data[i]; + cur >>= (j * 4); + cur &= 0xF; + name[i * 8 + j] = kHexToLiteral[cur]; + } + } +} + +UnityGUID StringToGUID (const std::string& guidString) +{ + return StringToGUID(guidString.c_str(), guidString.size()); +} + +UnityGUID StringToGUID (const char* guidString, size_t length) +{ + if (length != kGUIDStringLength) + return UnityGUID (); + + // Initialize hex char to int table + static char s_LiteralToHex[255]; + static bool s_IsInitialized = false; + if (!s_IsInitialized) + { + for (int i=0;i<255;i++) + s_LiteralToHex[i] = -1; + s_LiteralToHex['0'] = 0; + s_LiteralToHex['1'] = 1; + s_LiteralToHex['2'] = 2; + s_LiteralToHex['3'] = 3; + s_LiteralToHex['4'] = 4; + s_LiteralToHex['5'] = 5; + s_LiteralToHex['6'] = 6; + s_LiteralToHex['7'] = 7; + s_LiteralToHex['8'] = 8; + s_LiteralToHex['9'] = 9; + s_LiteralToHex['a'] = 10; + s_LiteralToHex['b'] = 11; + s_LiteralToHex['c'] = 12; + s_LiteralToHex['d'] = 13; + s_LiteralToHex['e'] = 14; + s_LiteralToHex['f'] = 15; + s_IsInitialized = true; + } + + // Convert every hex char into an int [0...16] + int hex[kGUIDStringLength]; + for (int i=0;i<kGUIDStringLength;i++) + hex[i] = s_LiteralToHex[guidString[i]]; + + UnityGUID guid; + for (int i=0;i<4;i++) + { + UInt32 cur = 0; + for (int j=8;j--;) + { + int curHex = hex[i * 8 + j]; + if (curHex == -1) + return UnityGUID (); + + cur |= (curHex << (j * 4)); + } + guid.data[i] = cur; + } + return guid; +} diff --git a/Runtime/Utilities/GUID.h b/Runtime/Utilities/GUID.h new file mode 100644 index 0000000..e310c1f --- /dev/null +++ b/Runtime/Utilities/GUID.h @@ -0,0 +1,73 @@ +#ifndef UNITY_GUID_H +#define UNITY_GUID_H + +#include "Runtime/Serialize/SerializeUtility.h" +#include <string> + +enum { kGUIDStringLength = 32 }; + +#define UNITY_HAVE_GUID_INIT (UNITY_OSX || UNITY_WIN || UNITY_IPHONE || UNITY_ANDROID || UNITY_LINUX || UNITY_PEPPER || UNITY_PS3 || UNITY_XENON || UNITY_BB10 || UNITY_TIZEN) + +// To setup the unique identifier use Init (). +// You can compare it against other unique identifiers +// It is guaranteed to be unique over space and time +// +// Called UnityGUID because Visual Studio really does not like structs named GUID! +struct UnityGUID +{ + UInt32 data[4]; + + // Used to be called GUID, so for serialization it has the old name + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (GUID) + + UnityGUID (UInt32 a, UInt32 b, UInt32 c, UInt32 d) { data[0] = a; data[1] = b; data[2] = c; data[3] = d; } + UnityGUID () { data[0] = 0; data[1] = 0; data[2] = 0; data[3] = 0; } + + bool operator == (const UnityGUID& rhs) const { + return data[0] == rhs.data[0] && data[1] == rhs.data[1] && data[2] == rhs.data[2] && data[3] == rhs.data[3]; + } + bool operator != (const UnityGUID& rhs) const { return !(*this == rhs); } + + bool operator < (const UnityGUID& rhs) const { return CompareGUID (*this, rhs) == -1; } + friend int CompareGUID (const UnityGUID& lhs, const UnityGUID& rhs); + + // Use this instead of guid != UnityGUID() -- Will often result in self-documented code + bool IsValid() const { return data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0; } + +#if UNITY_HAVE_GUID_INIT + void Init (); +#endif +}; + +std::string GUIDToString(const UnityGUID& guid); +void GUIDToString(const UnityGUID& guid, char* string); + +UnityGUID StringToGUID (const std::string& guidString); +UnityGUID StringToGUID (const char* guidString, size_t stringLength); + +inline int CompareGUID (const UnityGUID& lhs, const UnityGUID& rhs) +{ + for (int i=0;i<4;i++) + { + if (lhs.data[i] < rhs.data[i]) + return -1; + if (lhs.data[i] > rhs.data[i]) + return 1; + } + return 0; +} + +bool CompareGUIDStringLess (const UnityGUID& lhs, const UnityGUID& rhs); + +template<class TransferFunction> +void UnityGUID::Transfer (TransferFunction& transfer) +{ + TRANSFER (data[0]); + TRANSFER (data[1]); + TRANSFER (data[2]); + TRANSFER (data[3]); +} + +extern const char kHexToLiteral[16]; + +#endif diff --git a/Runtime/Utilities/GlobalPreferences.cpp b/Runtime/Utilities/GlobalPreferences.cpp new file mode 100644 index 0000000..fb741b0 --- /dev/null +++ b/Runtime/Utilities/GlobalPreferences.cpp @@ -0,0 +1,151 @@ +#include "UnityPrefix.h" + +#include "Runtime/Utilities/PlayerPrefs.h" + +#if UNITY_PLUGIN +#if UNITY_OSX +#include "OSXWebPluginUtility.h" +#endif // #if UNITY_OSX +#include "PlatformDependent/CommonWebPlugin/WebPluginUtility.h" +#endif // #if UNITY_PLUGIN + +#include "GlobalPreferences.h" + + +#if UNITY_WIN +#include "PlatformDependent/Win/Registry.h" +#if UNITY_EDITOR +const char* kRegistryPath = "Software\\Unity\\UnityEditor"; +#elif UNITY_STANDALONE +const char* kRegistryPath = "Software\\Unity\\UnityStandalone"; +#else +const char* kRegistryPath = "Software\\Unity\\WebPlayer"; +#endif +#endif // #if UNITY_WIN + +#if UNITY_OSX +#if UNITY_EDITOR +const char* kPrefsAppID = "com.unity3d.UnityEditor"; +#elif UNITY_STANDALONE +const char* kPrefsAppID = "com.unity3d.UnityStandalone"; +#else +const char* kPrefsAppID = "com.unity3d.UnityWebPlayer"; +#endif +#endif // #if UNITY_OSX + +#if UNITY_LINUX +#include "PlatformDependent/Linux/XmlOptions.h" +#include "Runtime/Utilities/FileUtilities.h" +#include "Runtime/Utilities/PathNameUtility.h" + +const char* kPrefsFileName = "global.prefs"; +#endif // #if UNITY_LINUX + + +std::string GetGlobalPreference(const char *key) +{ + std::string result; + + #if UNITY_WIN && !UNITY_WINRT + return registry::getString( kRegistryPath, key, "" ); + + #elif UNITY_OSX + CFStringRef cfPrefsAppID = CFStringCreateWithCString (NULL, kPrefsAppID, kCFStringEncodingASCII); + CFStringRef cfKey = CFStringCreateWithCString (NULL, key, kCFStringEncodingASCII); + CFStringRef val = (CFStringRef)CFPreferencesCopyAppValue (cfKey, cfPrefsAppID); + if (val) + { + result = CFStringToString(val); + CFRelease (val); + } + CFRelease (cfPrefsAppID); + CFRelease (cfKey); + + #elif UNITY_LINUX + std::string path = AppendPathName (GetUserConfigFolder (), kPrefsFileName); + XmlOptions options; + if (options.Load (path)) + { + result = options.GetString (key, ""); + } + + #elif GAMERELEASE + return PlayerPrefs::GetString (key); + + #else + // TODO + #endif + return result; +} + +bool GetGlobalBoolPreference(const char *key, bool defaultValue) +{ + std::string const pref = GetGlobalPreference (key); + if (pref == "yes") + return true; + if (pref == "no") + return false; + return defaultValue; +} + +void SetGlobalPreference(const char *key, std::string value) +{ + #if UNITY_WIN && !UNITY_WINRT + registry::setString( kRegistryPath, key, value.c_str() ); + + #elif UNITY_OSX + CFStringRef cfPrefsAppID = CFStringCreateWithCString (NULL, kPrefsAppID, kCFStringEncodingASCII); + CFStringRef cfKey = CFStringCreateWithCString (NULL, key, kCFStringEncodingASCII); + CFStringRef cfValue = CFStringCreateWithCString (NULL, value.c_str(), kCFStringEncodingASCII); + + CFPreferencesSetAppValue( cfKey, cfValue, cfPrefsAppID ); + CFPreferencesAppSynchronize( cfPrefsAppID ); + + #elif UNITY_LINUX + std::string path = AppendPathName (GetUserConfigFolder (), kPrefsFileName); + XmlOptions options; + options.Load (path); + options.SetString (key, value); + options.Save (path); + + #elif GAMERELEASE + PlayerPrefs::SetString (key, value); + + #else + // TODO + #endif +} + +void SetGlobalBoolPreference(const char *key, bool value) +{ + SetGlobalPreference (key, value?"yes":"no"); +} + + +#if WEBPLUG && !UNITY_PLUGIN +#include "Runtime/Misc/PlayerSettings.h" + +std::string GetStrippedPlayerDomain () +{ + std::string currentDomain = GetPlayerSettings().absoluteURL; + if (currentDomain.find("http://") == 0 || currentDomain.find("https://") == 0) + { + //remove http:// + if (currentDomain.find("http://") == 0) + currentDomain.erase(0, 7); + else if (currentDomain.find("https://") == 0) + currentDomain.erase(0, 8); + + //remove path + std::string::size_type pos = currentDomain.find("/", 0); + if (pos != std::string::npos) + currentDomain.erase(currentDomain.begin() + pos, currentDomain.end()); + + //remove port if present + pos = currentDomain.find(":", 0); + if (pos != std::string::npos) + currentDomain.erase(currentDomain.begin() + pos, currentDomain.end()); + } + return currentDomain; +} +#endif // #if WEBPLUG && !UNITY_PLUGIN diff --git a/Runtime/Utilities/GlobalPreferences.h b/Runtime/Utilities/GlobalPreferences.h new file mode 100644 index 0000000..b0ca58f --- /dev/null +++ b/Runtime/Utilities/GlobalPreferences.h @@ -0,0 +1,15 @@ +#ifndef GLOBALPREFERENCES_H +#define GLOBALPREFERENCES_H + +#if WEBPLUG +std::string GetStrippedPlayerDomain (); +#endif + +// Used for hardware stats and webplayer settings. +std::string GetGlobalPreference(const char *key); +bool GetGlobalBoolPreference(const char *key, bool defaultValue); + +void SetGlobalPreference(const char *key, std::string value); +void SetGlobalBoolPreference(const char *key, bool value); + +#endif
\ No newline at end of file diff --git a/Runtime/Utilities/Hash128.cpp b/Runtime/Utilities/Hash128.cpp new file mode 100644 index 0000000..8e8cb68 --- /dev/null +++ b/Runtime/Utilities/Hash128.cpp @@ -0,0 +1,33 @@ +#include "UnityPrefix.h" +#include "Hash128.h" + +std::string Hash128ToString(const Hash128& digest) +{ + std::string name; + name.resize (32); + for(int i=0; i < 16; i++) + sprintf(&name[i*2],"%02hhx",digest.hashData.bytes[i]); + return name; +} + +static inline int HexToNumber(char c) +{ + if( c >= '0' && c <= '9') + return c-'0'; + if( c >= 'a' && c <= 'f') + return c-'a'+10; + if( c >= 'A' && c <= 'F') + return c-'A'+10; + return 0; +} + + +Hash128 StringToHash128(const std::string& name) +{ + Hash128 digest; + int end = name.size() > 32 ? 16 : name.size()/2; + for( int i = 0; i < end; ++i ) { + digest.hashData.bytes[i] = HexToNumber(name[i*2]) * 16 + HexToNumber(name[i*2+1]); + } + return digest; +}
\ No newline at end of file diff --git a/Runtime/Utilities/Hash128.h b/Runtime/Utilities/Hash128.h new file mode 100644 index 0000000..ec659fe --- /dev/null +++ b/Runtime/Utilities/Hash128.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Runtime/Serialize/SerializeUtility.h" + +// Holds a 128 bit hash value +struct Hash128 +{ + union + { + unsigned char bytes[16]; + UInt64 u64[2]; + UInt32 u32[4]; + } hashData ; + + friend inline bool operator == (const Hash128& lhs, const Hash128& rhs) { return lhs.hashData.u64[0] == rhs.hashData.u64[0] && lhs.hashData.u64[1] == rhs.hashData.u64[1]; } + friend inline bool operator != (const Hash128& lhs, const Hash128& rhs) { return lhs.hashData.u64[0] != rhs.hashData.u64[0] || lhs.hashData.u64[1] != rhs.hashData.u64[1]; } + + Hash128 () { hashData.u64[0] = 0; hashData.u64[1] = 0; } + + DECLARE_SERIALIZE_NO_PPTR (Hash128) +}; + +// String conversion +std::string Hash128ToString(const Hash128& digest); +Hash128 StringToHash128(const std::string& name); + +// Serialization +template<class T> +void Hash128::Transfer (T& transfer) +{ + UInt8* bytes = hashData.bytes; + + TRANSFER(bytes[0]); + TRANSFER(bytes[1]); + TRANSFER(bytes[2]); + TRANSFER(bytes[3]); + TRANSFER(bytes[4]); + TRANSFER(bytes[5]); + TRANSFER(bytes[6]); + TRANSFER(bytes[7]); + TRANSFER(bytes[8]); + TRANSFER(bytes[9]); + TRANSFER(bytes[10]); + TRANSFER(bytes[11]); + TRANSFER(bytes[12]); + TRANSFER(bytes[13]); + TRANSFER(bytes[14]); + TRANSFER(bytes[15]); +}
\ No newline at end of file diff --git a/Runtime/Utilities/HashFunctions.h b/Runtime/Utilities/HashFunctions.h new file mode 100644 index 0000000..0f1f465 --- /dev/null +++ b/Runtime/Utilities/HashFunctions.h @@ -0,0 +1,87 @@ +#ifndef __HASH_FUNCTIONS_H +#define __HASH_FUNCTIONS_H + +#include <string> +#include "Runtime/Utilities/PathNameUtility.h" +#include "Runtime/Utilities/dense_hash_map.h" +#include "Runtime/Utilities/Word.h" +#include "Runtime/Allocator/MemoryMacros.h" + +struct InstanceIDHashFunctor +{ + inline size_t operator()(SInt32 x) const { + + UInt32 a = static_cast<UInt32> (x); + a = (a+0x7ed55d16) + (a<<12); + a = (a^0xc761c23c) ^ (a>>19); + a = (a+0x165667b1) + (a<<5); + a = (a+0xd3a2646c) ^ (a<<9); + a = (a+0xfd7046c5) + (a<<3); + a = (a^0xb55a4f09) ^ (a>>16); + + return a; + } +}; + +typedef pair<const SInt32, Object*> InstanceIdToObjectPair; +struct InstanceIdToObjectPtrHashMap : + dense_hash_map<SInt32, Object*, InstanceIDHashFunctor, std::equal_to<SInt32>, STL_ALLOCATOR( kMemSTL, InstanceIdToObjectPair ) > +{ + typedef dense_hash_map<SInt32, Object*, InstanceIDHashFunctor, std::equal_to<SInt32>, STL_ALLOCATOR( kMemSTL, InstanceIdToObjectPair )> base_t; + InstanceIdToObjectPtrHashMap (int n) + : base_t (n) + { + set_empty_key (-1); + set_deleted_key (-2); + } +}; + +// http://www.cse.yorku.ca/~oz/hash.html +// Other hashes here: http://burtleburtle.net/bob/ +struct djb2_hash +{ + inline size_t operator()(const std::string& str) const + { + unsigned long hash = 5381; + char c; + const char* s = str.c_str (); + + while ((c = *s++)) + hash = ((hash << 5) + hash) ^ c; + + return hash; + } +}; + +#if UNITY_EDITOR +struct djb2_hash_lowercase +{ + inline size_t operator() (const std::string& str) const + { + unsigned long hash = 5381; + char c; +#if UNITY_OSX + const char* s = NULL; + bool ascii = !RequiresNormalization (str); + if (ascii) + s = ToLower (str).c_str(); + else + s = ToLower (NormalizeUnicode (str, true)).c_str(); +#else + std::string help = ToLower(str); + const char* s = help.c_str(); +#endif + + while ((c = *s++)) + hash = ((hash << 5) + hash) ^ c; + + return hash; + } +}; +#endif + + +bool ComputeMD5Hash( const UInt8* data, size_t dataSize, UInt8 outHash[16] ); +bool ComputeSHA1Hash( const UInt8* data, size_t dataSize, UInt8 outHash[20] ); + +#endif diff --git a/Runtime/Utilities/InitializeAndCleanup.cpp b/Runtime/Utilities/InitializeAndCleanup.cpp new file mode 100644 index 0000000..77280f5 --- /dev/null +++ b/Runtime/Utilities/InitializeAndCleanup.cpp @@ -0,0 +1,51 @@ +#include "UnityPrefix.h" +#include "InitializeAndCleanup.h" + +enum { kMaxCount = 40 }; + +struct OrderedCallback +{ + int order; + RegisterRuntimeInitializeAndCleanup::CallbackFunction* init; + RegisterRuntimeInitializeAndCleanup::CallbackFunction* cleanup; +}; + +static int gNumRegisteredCallbacks = 0; +static OrderedCallback gCallbacks[kMaxCount]; + + +bool operator < (const OrderedCallback& lhs, const OrderedCallback& rhs) +{ + return lhs.order < rhs.order; +} + +RegisterRuntimeInitializeAndCleanup::RegisterRuntimeInitializeAndCleanup(CallbackFunction* Initialize, CallbackFunction* Cleanup, int order) +{ + gCallbacks[gNumRegisteredCallbacks].init = Initialize; + gCallbacks[gNumRegisteredCallbacks].cleanup = Cleanup; + gCallbacks[gNumRegisteredCallbacks].order = order; + + gNumRegisteredCallbacks++; + Assert(gNumRegisteredCallbacks <= kMaxCount); +} + +void RegisterRuntimeInitializeAndCleanup::ExecuteInitializations() +{ + std::sort (gCallbacks, gCallbacks + gNumRegisteredCallbacks); + + for (int i = 0; i < gNumRegisteredCallbacks; i++) + { + if (gCallbacks[i].init) + gCallbacks[i].init (); + } +} + +void RegisterRuntimeInitializeAndCleanup::ExecuteCleanup() +{ + for (int i = gNumRegisteredCallbacks-1; i >=0 ; i--) + { + if (gCallbacks[i].cleanup) + gCallbacks[i].cleanup (); + } +} + diff --git a/Runtime/Utilities/InitializeAndCleanup.h b/Runtime/Utilities/InitializeAndCleanup.h new file mode 100644 index 0000000..b079f47 --- /dev/null +++ b/Runtime/Utilities/InitializeAndCleanup.h @@ -0,0 +1,12 @@ +#pragma once + +class RegisterRuntimeInitializeAndCleanup +{ +public: + typedef void CallbackFunction (); + RegisterRuntimeInitializeAndCleanup(CallbackFunction* Initialize, CallbackFunction* Cleanup, int order = 0); + + static void ExecuteInitializations(); + static void ExecuteCleanup(); +}; + diff --git a/Runtime/Utilities/LODUtility.cpp b/Runtime/Utilities/LODUtility.cpp new file mode 100644 index 0000000..d2268fb --- /dev/null +++ b/Runtime/Utilities/LODUtility.cpp @@ -0,0 +1,86 @@ +#include "UnityPrefix.h" + +#include "LODUtility.h" +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Camera/LODGroup.h" +#include "Runtime/Filters/AABBUtility.h" +#include "Runtime/Camera/LODGroupManager.h" + +void CalculateLODGroupBoundingBox ( LODGroup& group ) +{ + Matrix4x4f worldToLocal = group.GetComponent(Transform).GetWorldToLocalMatrix(); + + MinMaxAABB minmax; + minmax.Init(); + for (int i=0;i<group.GetLODCount();i++) + { + for (int r=0;r<group.GetLOD(i).renderers.size();r++) + { + Renderer* renderer = group.GetLOD(i).renderers[r].renderer; + if (renderer && renderer->GetGameObjectPtr()) + { + AABB localBounds; + if (CalculateLocalAABB (renderer->GetGameObject(), &localBounds)) + { + Matrix4x4f relativeTransform; + Matrix4x4f rendererLocalToWorld = renderer->GetTransform().GetLocalToWorldMatrix(); + + MultiplyMatrices4x4(&worldToLocal, &rendererLocalToWorld, &relativeTransform); + + AABB lodGroupRelativeBoundds; + TransformAABBSlow (localBounds, relativeTransform, lodGroupRelativeBoundds); + + minmax.Encapsulate(lodGroupRelativeBoundds); + } + } + } + } + + float size; + if (minmax.IsValid()) + { + group.SetLocalReferencePoint (minmax.GetCenter()); + Vector3f extent = minmax.GetExtent() * 2.0F; + size = std::max(std::max(extent.x, extent.y), extent.z); + } + else + { + group.SetLocalReferencePoint (Vector3f (0, 0, 0)); + size = 1; + } + + float scale = group.GetWorldSpaceScale(); + if (scale > 0.0001F) + size /= scale; + + group.SetSize (size); +} + +void ForceLODLevel (const LODGroup& group, int index) +{ + int LODCount = group.GetLODCount(); + if (index >= LODCount) + if (index >= LODCount) + { + WarningString("SetLODs: Attempting to force a LOD outside the number available LODs"); + return; + } + + // mask of 0 = no force + // now create a mask for the rest + UInt32 lodMask = 0; + if (index >= 0) + lodMask = 1 << index; + + LODGroupManager& m = GetLODGroupManager(); + int lodGroupIndex = group.GetLODGroup(); + if (lodGroupIndex < 0) + { + WarningString("SetLODs: Attempting to force a LOD outside the number available LODs"); + return; + } + + m.SetForceLODMask (lodGroupIndex, lodMask); +} + diff --git a/Runtime/Utilities/LODUtility.h b/Runtime/Utilities/LODUtility.h new file mode 100644 index 0000000..58ff47d --- /dev/null +++ b/Runtime/Utilities/LODUtility.h @@ -0,0 +1,7 @@ +#pragma once + +class LODGroup; + +void CalculateLODGroupBoundingBox (LODGroup& group); + +void ForceLODLevel (const LODGroup& group, int index); diff --git a/Runtime/Utilities/LinkedList.h b/Runtime/Utilities/LinkedList.h new file mode 100644 index 0000000..f2d7d3f --- /dev/null +++ b/Runtime/Utilities/LinkedList.h @@ -0,0 +1,385 @@ +#ifndef LINKED_LIST_H +#define LINKED_LIST_H + +#if !UNITY_RELEASE + #define LINKED_LIST_ASSERT(x) Assert(x) +#else + #define LINKED_LIST_ASSERT(x) +#endif + +class ListElement +{ +public: + inline ListElement(); + inline ~ListElement() { RemoveFromList(); } + + inline bool IsInList() const; + inline bool RemoveFromList(); + inline void InsertInList(ListElement* pos); + + // Check against List::end(), not NULL + ListElement* GetPrev() const { return m_Prev; } + ListElement* GetNext() const { return m_Next; } + +private: + // Non copyable + ListElement(const ListElement&); + ListElement& operator=(const ListElement&); + + ListElement* m_Prev; + ListElement* m_Next; + + template <class T> friend class List; + inline void ValidateLinks() const; + +#if !UNITY_RELEASE + // Iterator debugging only + template <class T> friend class ListIterator; + template <class T> friend class ListConstIterator; + void SetList(void* l) { m_List = l; } + void* m_List; +#else + void SetList(void*) {} +#endif +}; + +template <class T> +class ListNode : public ListElement +{ +public: + ListNode(T* data = NULL) : m_Data(data) {} + T& operator*() const { return *m_Data; } + T* operator->() const { return m_Data; } + T* GetData() const { return m_Data; } + void SetData(T* data) { m_Data = data; } + + // We know the type of prev and next element + ListNode* GetPrev() const { return static_cast<ListNode*>(ListElement::GetPrev()); } + ListNode* GetNext() const { return static_cast<ListNode*>(ListElement::GetNext()); } + +private: + T* m_Data; +}; + +template <class T> +class ListIterator +{ +public: + ListIterator(T* node = NULL) : m_Node(node) {} + + // Pre- and post-increment operator + ListIterator& operator++() { m_Node = m_Node->GetNext(); return *this; } + ListIterator operator++(int) { ListIterator ret(*this); ++(*this); return ret; } + + // Pre- and post-decrement operator + ListIterator& operator--() { m_Node = m_Node->GetPrev(); return *this; } + ListIterator operator--(int) { ListIterator ret(*this); --(*this); return ret; } + + T& operator*() const { return static_cast<T&>(*m_Node); } + T* operator->() const { return static_cast<T*>(m_Node); } + + friend bool operator !=(const ListIterator& x, const ListIterator& y) { return x.m_Node != y.m_Node; } + friend bool operator ==(const ListIterator& x, const ListIterator& y) { return x.m_Node == y.m_Node; } + +private: + template <class S> friend class List; + ListIterator(ListElement* node) : m_Node(node) {} + ListElement* m_Node; +}; + + +template <class T> +class ListConstIterator +{ +public: + ListConstIterator(const T* node = NULL) : m_Node(node) {} + + // Pre- and post-increment operator + ListConstIterator& operator++() { m_Node = m_Node->GetNext(); return *this; } + ListConstIterator operator++(int) { ListConstIterator ret(*this); ++(*this); return ret; } + + // Pre- and post-decrement operator + ListConstIterator& operator--() { m_Node = m_Node->GetPrev(); return *this; } + ListConstIterator operator--(int) { ListConstIterator ret(*this); --(*this); return ret; } + + const T& operator*() const { return static_cast<const T&>(*m_Node); } + const T* operator->() const { return static_cast<const T*>(m_Node); } + + friend bool operator !=(const ListConstIterator& x, const ListConstIterator& y) { return x.m_Node != y.m_Node; } + friend bool operator ==(const ListConstIterator& x, const ListConstIterator& y) { return x.m_Node == y.m_Node; } + +private: + template <class S> friend class List; + ListConstIterator(const ListElement* node) : m_Node(node) {} + const ListElement* m_Node; +}; + +template <class T> +class List +{ +public: + typedef ListConstIterator<T> const_iterator; + typedef ListIterator<T> iterator; + typedef T value_type; + + inline List(); + inline ~List(); + + void push_back(T& node) { node.InsertInList(&m_Root); } + void push_front(T& node) { node.InsertInList(m_Root.m_Next); } + void insert(iterator pos, T& node) { node.InsertInList(&(*pos)); } + void erase(iterator pos) { pos->RemoveFromList(); } + + void pop_back() { if (m_Root.m_Prev != &m_Root) m_Root.m_Prev->RemoveFromList(); } + void pop_front() { if (m_Root.m_Next != &m_Root) m_Root.m_Next->RemoveFromList(); } + + iterator begin() { return iterator(m_Root.m_Next); } + iterator end() { return iterator(&m_Root); } + + const_iterator begin() const { return const_iterator(m_Root.m_Next); } + const_iterator end() const { return const_iterator(&m_Root); } + + T& front() { LINKED_LIST_ASSERT(!empty()); return static_cast<T&>(*m_Root.m_Next); } + T& back() { LINKED_LIST_ASSERT(!empty()); return static_cast<T&>(*m_Root.m_Prev); } + + const T& front() const { LINKED_LIST_ASSERT(!empty()); return static_cast<const T&>(*m_Root.m_Next); } + const T& back() const { LINKED_LIST_ASSERT(!empty()); return static_cast<const T&>(*m_Root.m_Prev); } + + bool empty() const { return begin() == end(); } + + size_t size_slow() const; + inline void clear(); + inline void swap(List& other); + + // Insert list into list (removes elements from source) + inline void insert(iterator pos, List& src); + inline void append(List& src); + +private: + ListElement m_Root; +}; + + +template <class T> +List<T>::List() +{ + m_Root.m_Prev = &m_Root; + m_Root.m_Next = &m_Root; + m_Root.SetList(this); +} + +template <class T> +List<T>::~List() +{ + clear(); +} + +template <class T> +size_t List<T>::size_slow () const +{ + size_t size = 0; + ListElement* node = m_Root.m_Next; + while (node != &m_Root) + { + node = node->m_Next; + size++; + } + return size; +} + +template <class T> +void List<T>::clear() +{ + ListElement* node = m_Root.m_Next; + while (node != &m_Root) + { + ListElement* next = node->m_Next; + node->m_Prev = NULL; + node->m_Next = NULL; + node->SetList(NULL); + node = next; + } + m_Root.m_Next = &m_Root; + m_Root.m_Prev = &m_Root; +} + +template <class T> +void List<T>::swap(List<T>& other) +{ + LINKED_LIST_ASSERT(this != &other); + + std::swap(other.m_Root.m_Prev, m_Root.m_Prev); + std::swap(other.m_Root.m_Next, m_Root.m_Next); + + if (other.m_Root.m_Prev == &m_Root) + other.m_Root.m_Prev = &other.m_Root; + if (m_Root.m_Prev == &other.m_Root) + m_Root.m_Prev = &m_Root; + if (other.m_Root.m_Next == &m_Root) + other.m_Root.m_Next = &other.m_Root; + if (m_Root.m_Next == &other.m_Root) + m_Root.m_Next = &m_Root; + + other.m_Root.m_Prev->m_Next = &other.m_Root; + other.m_Root.m_Next->m_Prev = &other.m_Root; + + m_Root.m_Prev->m_Next = &m_Root; + m_Root.m_Next->m_Prev = &m_Root; + +#if !UNITY_RELEASE + iterator my_it, my_end = end(); + for (my_it = begin(); my_it != my_end; ++my_it) + my_it->m_List = this; + iterator other_it, other_end = other.end(); + for (other_it = other.begin(); other_it != other_end; ++other_it) + other_it->m_List = &other; +#endif +} + +template <class T> +void List<T>::insert(iterator pos, List<T>& src) +{ + LINKED_LIST_ASSERT(this != &src); + if (src.empty()) + return; + +#if !UNITY_RELEASE + iterator src_it, src_end = src.end(); + for (src_it = src.begin(); src_it != src_end; ++src_it) + src_it->m_List = this; +#endif + // Insert source before pos + ListElement* a = pos.m_Node->m_Prev; + ListElement* b = pos.m_Node; + a->m_Next = src.m_Root.m_Next; + b->m_Prev = src.m_Root.m_Prev; + a->m_Next->m_Prev = a; + b->m_Prev->m_Next = b; + // Clear source list + src.m_Root.m_Next = &src.m_Root; + src.m_Root.m_Prev = &src.m_Root; +} + +template <class T> +void List<T>::append(List& src) +{ + insert(end(), src); +} + +ListElement::ListElement() +{ + m_Prev = NULL; + m_Next = NULL; + SetList(NULL); +} + +bool ListElement::IsInList() const +{ + return m_Prev != NULL; +} + +bool ListElement::RemoveFromList() +{ + if (!IsInList()) + return false; + +#if !UNITY_RELEASE + ValidateLinks(); +#endif + m_Prev->m_Next = m_Next; + m_Next->m_Prev = m_Prev; + m_Prev = NULL; + m_Next = NULL; + return true; +} + +void ListElement::InsertInList(ListElement* pos) +{ + if (this == pos) + return; + + if (IsInList()) + RemoveFromList(); + +#if !UNITY_RELEASE + m_List = pos->m_List; + pos->m_Prev->ValidateLinks(); + pos->ValidateLinks(); +#endif + m_Prev = pos->m_Prev; + m_Next = pos; + m_Prev->m_Next = this; + m_Next->m_Prev = this; +#if !UNITY_RELEASE + ValidateLinks(); +#endif + return; +} + +void ListElement::ValidateLinks() const +{ +#if !UNITY_RELEASE + LINKED_LIST_ASSERT(m_Prev != NULL && m_Next != NULL); + LINKED_LIST_ASSERT(m_Prev->m_Next == this && m_Next->m_Prev == this); + LINKED_LIST_ASSERT(m_Prev->m_List == m_List && m_Next->m_List == m_List); +#endif +} + +/// Allows for iterating a linked list, even if you add / remove any node during traversal. +template<class T> +class SafeIterator +{ +public: + SafeIterator(T& list) + : m_SourceList(list) + { + m_CurrentNode = NULL; + m_ExecuteList.swap(m_SourceList); + } + + ~SafeIterator() + { + // Call Complete if you abort the iteration! + LINKED_LIST_ASSERT(m_ExecuteList.empty()); + } + + // You must call complete if you are in some way aborting list iteration. + // If you dont call Complete, the source list will lose nodes that have not yet been iterated permanently. + // + /// SafeIterator<Behaviour*> i(myList); + /// i =0; + /// while(i.GetNext() && ++i != 3) + /// (**i).Update(); + /// i.Complete(); + void Complete() + { + m_SourceList.append(m_ExecuteList); + } + + typename T::value_type* Next() + { + if(!m_ExecuteList.empty()) + { + typename T::iterator it = m_ExecuteList.begin(); + m_CurrentNode = &*it; + m_ExecuteList.erase(it); + m_SourceList.push_back(*m_CurrentNode); + } + else + { + m_CurrentNode = NULL; + } + return m_CurrentNode; + } + + typename T::value_type& operator *() const { return *m_CurrentNode; } + typename T::value_type* operator ->() const { return m_CurrentNode; } + +private: + T m_ExecuteList; + T& m_SourceList; + typename T::value_type* m_CurrentNode; +}; + + +#endif diff --git a/Runtime/Utilities/LogAssert.cpp b/Runtime/Utilities/LogAssert.cpp new file mode 100644 index 0000000..6a863b3 --- /dev/null +++ b/Runtime/Utilities/LogAssert.cpp @@ -0,0 +1,1294 @@ +#include "UnityPrefix.h" +#include "LogAssert.h" +#include <stdarg.h> +#include "PathNameUtility.h" +#include <sys/stat.h> +#include <list> +#include "Configuration/UnityConfigure.h" +#include "Runtime/Threads/AtomicOps.h" +#if !UNITY_EXTERNAL_TOOL && !UNITY_PLUGIN +#include "Runtime/Threads/Thread.h" +#include "Runtime/Threads/Mutex.h" +#include "Runtime/Threads/ThreadSpecificValue.h" +#include "Runtime/Misc/PlayerSettings.h" +#endif +#include "Runtime/Profiler/Profiler.h" + +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_TIZEN +#include <syslog.h> +#include <sys/fcntl.h> +#include <unistd.h> +#include "Runtime/Utilities/File.h" +#endif +#if UNITY_NACL +#include "PlatformDependent/PepperPlugin/UnityInstance.h" +#endif + +#if UNITY_BB10 +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#endif +#if UNITY_WIN +#if !UNITY_WP8 +#include <ShlObj.h> +#endif +#include "PlatformDependent/Win/PathUnicodeConversion.h" +#include "FileUtilities.h" +#include "PlatformDependent/Win/WinUtils.h" +#include "io.h" +#endif + +#if UNITY_TIZEN +#include <osp/FBaseLog.h> +#endif +#if UNITY_EDITOR +#include "File.h" +#include "Editor/Src/Utility/Analytics.h" +#endif + +#if WEBPLUG +#include "ErrorExit.h" +#endif + +#include "Runtime/Scripting/ScriptingUtility.h" + +#if UNITY_WII +#include "Platformdependent/wii/WiiDbgUtils.h" +#endif +#if UNITY_ANDROID +#include <android/log.h> + +int CsToAndroid[LogType_NumLevels] = +{ + ANDROID_LOG_ERROR, // LogType_Error = 0, + ANDROID_LOG_ERROR, // LogType_Assert = 1, + ANDROID_LOG_WARN, // LogType_Warning = 2, + ANDROID_LOG_INFO, // LogType_Log = 3, + ANDROID_LOG_ERROR, // LogType_Exception = 4, + ANDROID_LOG_DEBUG // LogType_Debug = 5, +}; +#endif + +using namespace std; + +#if UNITY_WP8 + #include "PlatformDependent\MetroPlayer\MetroUtils.h" +#endif + +#if WEBPLUG && !UNITY_WIN +#define CAP_LOG_OUTPUT_SIZE 1 +#else +#define CAP_LOG_OUTPUT_SIZE 0 +#endif + +#if UNITY_WIN && DEBUGMODE && !UNITY_WP8 + extern "C" WINBASEAPI BOOL WINAPI IsDebuggerPresent( VOID ); + #define LOG_TO_WINDOWS_DEBUGGER \ + char buf[4096]; buf[4095]=0; \ + vsnprintf( buf, sizeof(buf)-1, log, list ); \ + OutputDebugStringA( buf ) +#elif UNITY_WP8 && UNITY_DEVELOPER_BUILD + #define LOG_TO_WINDOWS_DEBUGGER \ + char buf[4096]; buf[4095]=0; \ + vsnprintf (buf, sizeof(buf) - 1, log, list); \ + auto str = ConvertUtf8ToString (buf); \ + s_WinRTBridge->WP8Utility->OutputLogMessage (str) +#else + #define LOG_TO_WINDOWS_DEBUGGER +#endif + +#if UNITY_FLASH +extern "C" void Ext_Flash_LogCallstack(); +#endif + +static LogEntryHandler gCurrentLogEntryHandler = NULL; +static std::list<LogEntryHandler> *gCleanLogEntryHandlers = NULL; + +void ReleaseLogHandlers() +{ + if (gCleanLogEntryHandlers != NULL) + { + delete gCleanLogEntryHandlers; + gCleanLogEntryHandlers = NULL; + } +} + +void SetLogEntryHandler(LogEntryHandler newHandler) +{ + gCurrentLogEntryHandler = newHandler; +} + +void AddCleanLogEntryHandler(LogEntryHandler newHandler) +{ + if (gCleanLogEntryHandlers == NULL) + gCleanLogEntryHandlers = new std::list<LogEntryHandler>(); + + gCleanLogEntryHandlers->push_back(newHandler); +} + +FILE* gConsoleFile = NULL; +FILE* gReproductionLogFile = NULL; + +bool DefaultCleanLogHandlerv (LogType logType, const char* log, va_list list); + +void InitializeCleanedLogFile (FILE* file) +{ + Assert(gReproductionLogFile == NULL); + Assert(gCleanLogEntryHandlers == NULL || count(gCleanLogEntryHandlers->begin(), gCleanLogEntryHandlers->end(), &DefaultCleanLogHandlerv) == 0); + + gReproductionLogFile = file; + + AddCleanLogEntryHandler(&DefaultCleanLogHandlerv); +} + +static StaticString gConsolePath; +#if CAP_LOG_OUTPUT_SIZE +static int gConsoleSizeCheck = 0; +#endif +#if !UNITY_EXTERNAL_TOOL +static UNITY_TLS_VALUE(int) gRecursionLock; +#endif + +enum { kMaxLogSize = 2000000 }; + + + +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN +fpos_t gStdOutPosition; +int gStdoutFd; +void ResetStdout() +{ + fflush(stdout); + dup2(gStdoutFd, fileno(stdout)); + close(gStdoutFd); + clearerr(stdout); + fsetpos(stdout, &gStdOutPosition); +} +#endif + +#if UNITY_XENON || UNITY_PS3 +void LogOutputToSpecificFile (const char* path) +{ + if (path == NULL) + return; + + gConsolePath = path; + gConsoleFile = fopen(path, "w"); + if(gConsoleFile) + fclose(gConsoleFile); + +} +#elif !UNITY_PS3 && !UNITY_ANDROID && !UNITY_PEPPER && !UNITY_FLASH + +void LogOutputToSpecificFile (const char* path) +{ +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN + + // Save the stdout position for later + fgetpos(stdout, &gStdOutPosition); + gStdoutFd = dup(fileno(stdout)); + + if (path == NULL || strlen(path) == 0) + { + gConsoleFile = stdout; + } + else + { + gConsoleFile = fopen(path, "w"); + int fd = open(path, O_WRONLY, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); + } + +#elif UNITY_WII + + // TODO: see if logging to the host machine (windows) works + if (path == NULL) + return; + + gConsolePath = path; + gConsoleFile = fopen(path, "w"); + +#else + + // On windows just default to editor.log instead + if (path == NULL) + return; + + gConsolePath = path; + gConsoleFile = fopen(path, "w"); +#endif +} +#endif + +#if SUPPORT_ENVIRONMENT_VARIABLES +std::string GetStringFromEnv(const char *envName) +{ + const char* env = getenv( envName ); + std::string result; + if( env != NULL && env[0] != 0 ) + result.append(env); + return result; +} +std::string GetCustomLogFile() +{ + return GetStringFromEnv("UNITY_LOG_FILE"); +} +std::string GetCleanedLogFile() +{ + return GetStringFromEnv("UNITY_CLEANED_LOG_FILE"); +} +#endif + +#if UNITY_WIN + +string SetLogFilePath(string const& path) +{ + Assert(gConsolePath.empty()); + + gConsolePath = path; + + #if !UNITY_EDITOR && !UNITY_EXTERNAL_TOOL && !UNITY_WINRT + + if (!gConsolePath.empty()) + { + string const customLogFile = GetCustomLogFile(); + + if (!customLogFile.empty()) + { + gConsolePath = customLogFile.c_str(); + } + } + + #endif + + return gConsolePath.c_str(); +} + +namespace +{ + int gStdOutFd = -1; + int gStdErrFd = -1; + FILE* gStdOutFile = NULL; + FILE* gStdErrFile = NULL; + + void CloseConsoleWin() + { + gConsoleFile = NULL; + + if (NULL != gStdOutFile) + { + int const result = fclose(gStdOutFile); + //Assert(0 == result); + + gStdOutFile = NULL; + } + + if (NULL != gStdErrFile) + { + int const result = fclose(gStdErrFile); + //Assert(0 == result); + + gStdErrFile = NULL; + } + + if (-1 != gStdOutFd) + { + int const result = _dup2(gStdOutFd, 1); + //Assert(0 == result); + + gStdOutFd = -1; + } + + if (-1 != gStdErrFd) + { + int const result = _dup2(gStdErrFd, 2); + //Assert(0 == result); + + gStdErrFd = -1; + } + gConsolePath.clear(); + } + + void OpenConsoleWin() + { + // don't assert in this function because it might cause stack overflow + std::wstring widePath; + + //Assert(NULL == gConsoleFile); + + // check for no log file + + if (gConsolePath.empty()) + { + gConsoleFile = stdout; + return; + } + + // duplicate stdout and stderr file descriptors so they can be restored later + + gStdOutFd = _dup(1); + //Assert(-1 != gStdOutFd); + + if (-1 == gStdOutFd) + { + goto error; + } + + gStdErrFd = _dup(2); + //Assert(-1 != gStdErrFd); + + if (-1 == gStdErrFd) + { + goto error; + } + + // reassign stdout and stderr file pointers + + ConvertUnityPathName(gConsolePath, widePath); + + gStdOutFile = _wfreopen(widePath.c_str(), L"a", stdout); + //Assert(NULL != gStdOutFile); + + if (NULL == gStdOutFile) + { + goto error; + } + + gStdErrFile = _wfreopen(widePath.c_str(), L"a", stderr); + //Assert(NULL != gStdErrFile); + + if (NULL == gStdErrFile) + { + goto error; + } + + // redirect stderr to stdout + + int const error = _dup2(1, 2); + //Assert(0 == error); + + if (0 != error) + { + goto error; + } + + // disable stdout and stderr buffering + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + // done + + gConsoleFile = stdout; + return; + + // failed + + error: + + CloseConsoleWin(); + + gConsoleFile = stdout; + return; + } +} + +#endif + +#if (UNITY_OSX || UNITY_LINUX) && !UNITY_EXTERNAL_TOOL +// Get the logfile path, relative to the user's home directory, using a system-specific subpath +static std::string GetHomedirLogfile (const std::string &relativePath) +{ + string folder = getenv ("HOME"); + if (folder.empty ()) + return ""; + + // Create log file and parent folders + folder = AppendPathName( folder, relativePath); + CreateDirectoryRecursive (folder); + + #if UNITY_EDITOR + std::string result = AppendPathName( folder, "Editor.log" ); + // move any existing log file into Editor-prev.log + if( IsFileCreated(result) ) + MoveReplaceFile( result, AppendPathName(folder,"Editor-prev.log" ) ); + #else + std::string result = GetCustomLogFile(); + if (result.empty()) + result = AppendPathName( folder, "Player.log" ); + #endif + + return result; +} + +// Open the console path and redirect stdout/stderr if appropriate +static void OpenConsoleFile () +{ + #if UNITY_EDITOR + gConsoleFile = fopen(gConsolePath.c_str(), "w"); + #else + gConsoleFile = fopen(gConsolePath.c_str(), "w+"); + #endif + + #if !WEBPLUG + // Save the stdout position for later + fgetpos(stdout, &gStdOutPosition); + gStdoutFd = dup(fileno(stdout)); + + if (gConsoleFile) + { + int fd = fileno (gConsoleFile); + + if (dup2 (fd, fileno (stdout)) < 0) + fprintf (stderr, "Failed to redirect stdout to the console file %s.\n", gConsolePath.c_str ()); + if (dup2 (fd, fileno (stderr)) < 0) + fprintf (stderr, "Failed to redirect stderr to the console file %s.\n", gConsolePath.c_str ()); + } + #endif +} +#endif + +FILE* OpenConsole () +{ + if (gConsoleFile != NULL) + return gConsoleFile; + + #define PLATFORM_ALWAYS_USES_STDOUT_FOR_LOG (UNITY_XENON || UNITY_IPHONE || UNITY_ANDROID || UNITY_PEPPER || UNITY_FLASH || UNITY_WII || UNITY_PS3 || UNITY_EXTERNAL_TOOL || UNITY_WEBGL || UNITY_BB10 || UNITY_TIZEN || ENABLE_GFXDEVICE_REMOTE_PROCESS_WORKER ) + + #if GAMERELEASE && !UNITY_EXTERNAL_TOOL && !WEBPLUG && !UNITY_PLUGIN && !PLATFORM_ALWAYS_USES_STDOUT_FOR_LOG + if (GetPlayerSettingsPtr() == NULL) + return stdout; + + if (!GetPlayerSettings().GetUsePlayerLog()) + { + gConsoleFile = stdout; + return gConsoleFile; + } + #endif + + #if PLATFORM_ALWAYS_USES_STDOUT_FOR_LOG + + #if UNITY_NACL + // in nacl, we can't write to a custom log location. + // so if we need a clean log, use stdout for that, and use 0 for the + // normal, non-clean log. + if (GetUnityInstance().GetCleanLog()) + return 0; + #endif + + gConsoleFile = stdout; + + #elif UNITY_OSX + gConsolePath = GetHomedirLogfile ("Library/Logs/Unity"); + if (!gConsolePath.empty()) + OpenConsoleFile (); + + #elif UNITY_LINUX + gConsolePath = GetHomedirLogfile (".config/unity3d"); + if (!gConsolePath.empty()) + OpenConsoleFile (); + + #elif UNITY_WIN + + OpenConsoleWin(); + + #elif UNITY_PS3 + gConsoleFile = stdout; + // When running from a read only file system, stdout is a valid file pointer, but it crashes later + // if trying to write into it. So we check if valid _fileno exists for it. + if( gConsoleFile && fileno(gConsoleFile) < 0 ) + gConsoleFile = NULL; + + #else + + #error "Unknown platform" + + #endif + + return gConsoleFile; +} + +#if UNITY_WIN +void CloseConsoleFile() +{ + CloseConsoleWin(); +} +#endif + +#if UNITY_EDITOR +string GetEditorConsoleLogPath () +{ + #if UNITY_OSX + + string home = getenv ("HOME"); + return AppendPathName (home, "Library/Logs/Unity/Editor.log"); + + #elif UNITY_WIN + + return gConsolePath.c_str(); + + #elif UNITY_LINUX + + string home = getenv ("HOME"); + return AppendPathName (home, ".config/Unity/Editor/Editor.log"); + + #else + #error "Unknown platform" + #endif +} + +string GetMonoDevelopLogPath () +{ + #if UNITY_OSX + + string result = getenv ("HOME"); + return AppendPathName( result, "Library/Logs/MonoDevelop/MonoDevelop.log"); + + #elif UNITY_WIN + + wchar_t widePath[MAX_PATH]; + if( SUCCEEDED(SHGetFolderPathW( NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, widePath )) ) + { + std::string folder; + ConvertWindowsPathName( widePath, folder ); + folder = AppendPathName( folder, "MonoDevelop-Unity" ); + return AppendPathName( folder, "log.txt" ); + } + + #elif UNITY_LINUX + + string result = getenv ("HOME"); + return AppendPathName( result, ".config/MonoDevelop-Unity/log.txt"); + + #else + #error "Unknown platform" + #endif + + return ""; +} + +#if UNITY_OSX +string GetPlayerConsoleLogPath () +{ + string home = getenv ("HOME"); + return AppendPathName (home, "Library/Logs/Unity/Player.log"); +} +#endif + +#if UNITY_LINUX +string GetPlayerConsoleLogPath () +{ + string home = getenv ("HOME"); + return AppendPathName (home, ".config/unity3d/Editor/Player.log"); +} +#endif +#endif + +string GetConsoleLogPath () +{ + return gConsolePath.c_str(); +} + +#if (UNITY_XENON || UNITY_PS3) && !MASTER_BUILD +static Mutex s_mutex; +#endif + +void printf_consolev (LogType logType, const char* log, va_list alist) +{ + va_list list; + va_copy (list, alist); + + if (gCurrentLogEntryHandler && !gCurrentLogEntryHandler (logType, log, list)) + return; + +#if UNITY_FLASH + char buffer[1024 * 10]; + vsnprintf (buffer, 1024 * 10, log, list); + Ext_Trace(buffer); + + va_end (list); + return; +#endif + +#if UNITY_ANDROID + if (gReproductionLogFile == NULL) // gReproductionLogFile / 'cleanedLogFile' should remove all other logging + { + if (ENABLE_PROFILER /* == development player */ || logType < LogType_Debug) + __android_log_vprint(CsToAndroid[logType], "Unity", log, list); + } + + va_end (list); + return; +#endif + +#if UNITY_TIZEN + if (gReproductionLogFile == NULL) + { + static char buffer[1024 * 10]; + memset(buffer, 0, 1024*10); + vsnprintf (buffer, 1024 * 10, log, list); + AppLogTagInternal("Unity", "", 0, buffer); + } +#endif + +#if UNITY_XENON +#if !MASTER_BUILD + Mutex::AutoLock lock(s_mutex); + + char buffer[1024 * 8] = { 0 }; + vsnprintf(buffer, 1024 * 8, log, list); + + gConsoleFile = fopen(gConsolePath.c_str(), "a"); + if(gConsoleFile) + { + fprintf(gConsoleFile, buffer); + fflush(gConsoleFile); + fclose(gConsoleFile); + } + + OutputDebugString(buffer); + +#endif + va_end (list); + return; +#endif + +#if UNITY_PS3 +#if !MASTER_BUILD + Mutex::AutoLock lock(s_mutex); + + gConsoleFile = fopen(gConsolePath.c_str(), "a"); + if(gConsoleFile) + { + vfprintf (gConsoleFile, log, list); + fflush( gConsoleFile ); + fclose( gConsoleFile ); + vfprintf (stdout, log, list); + } + else + { + vfprintf(stdout, log, list); + fflush(stdout); + } + +#endif + va_end (list); + return; +#endif + +#if UNITY_WII + #if !MASTER_BUILD + vfprintf (stdout, log, list); + fflush (stdout); + va_end (list); + #endif + return; +#endif + + if (gConsoleFile == NULL) + { + if (OpenConsole() == NULL) { + va_end (list); + return; + } + } + + if (gConsoleFile) + { + vfprintf (gConsoleFile, log, list); + fflush( gConsoleFile ); + + // Clamp the size of the file to 100kb with a rolling buffer. + // copy last 50kb to beginning of file. + #if CAP_LOG_OUTPUT_SIZE + gConsoleSizeCheck++; + if (gConsoleSizeCheck > 20) + { + gConsoleSizeCheck = 0; + struct stat statbuffer; + if( ::stat(gConsolePath.c_str(), &statbuffer) == 0 && statbuffer.st_size > kMaxLogSize) + { + FILE* file = fopen(gConsolePath.c_str(), "r"); + if (file) + { + fseek(file, statbuffer.st_size - kMaxLogSize / 2, SEEK_SET); + UInt8* buffer = new UInt8[kMaxLogSize / 2]; + if (fread(buffer, 1, kMaxLogSize / 2, file) == kMaxLogSize / 2) + { + fclose(file); + fclose(gConsoleFile); + gConsoleFile = fopen(gConsolePath.c_str(), "w"); + fwrite(buffer, 1, kMaxLogSize / 2, gConsoleFile); + } + else + { + fclose(file); + } + delete[] buffer; + } + } + } + #endif + } + else + { +#ifndef DISABLE_TTY + vfprintf (stdout, log, list); + fflush( stdout ); +#endif + } + + LOG_TO_WINDOWS_DEBUGGER; + va_end (list); +} + +extern "C" void printf_console_log(const char* log, va_list list) +{ + printf_consolev(LogType_Log, log, list); +} + +extern "C" void printf_console (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Debug, log, vl); + va_end(vl); +} + +static void InternalLogConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Log, log, vl); + va_end(vl); +} + +static void InternalWarningConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Warning, log, vl); + va_end(vl); +} + +static void InternalAssertConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Assert, log, vl); + va_end(vl); +} + +static void InternalErrorConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Error, log, vl); + va_end(vl); +} + +static void InternalIgnoreConsole (const char* log, ...) +{ +} + +static LogToConsoleImpl* gLogToConsoleFunc = NULL; +static LogCallback* gLogCallbackFunc = NULL; +static bool gLogCallbackFuncThreadSafe = false; +static PreprocessCondition* gPreprocessor = NULL; +static RemoveLogFunction* gRemoveLog = NULL; +static RemoveLogFunction* gShowLogWithMode = NULL; +extern "C" void __msl_assertion_failed(char const *condition, char const *filename, char const *funcname, int lineno); + +/* +extern "C" +{ +void __eprintf(const char* log, ...) +{ +printf_console (log, va_list(&log + 1)); +} + +void malloc_printf (const char* log, ...) +{ + printf_console (log, va_list(&log + 1)); +} +} +*/ +void RegisterLogToConsole (LogToConsoleImpl* func) +{ + gLogToConsoleFunc = func; +} + +void RegisterLogCallback (LogCallback* callback, bool threadsafe) +{ + gLogCallbackFunc = callback; + gLogCallbackFuncThreadSafe = threadsafe; +} + +void RegisterLogPreprocessor (PreprocessCondition* func) +{ + gPreprocessor = func; +} + +void RegisterRemoveImportErrorFromConsole (RemoveLogFunction* func) +{ + gRemoveLog = func; +} + +void RegisterShowErrorWithMode (RemoveLogFunction* func) +{ + gShowLogWithMode = func; +} + + +extern "C" void __msl_assertion_failed(char const *condition, char const *filename, char const *funcname, int lineno) +{ + DebugStringToFile (condition, 0, filename, lineno, kAssert, 0); +} + +inline bool ContainsNewLine (const char* c) +{ + while (*c != '\0') + { + if (*c == '\n') + return true; + c++; + } + return false; +} + + +static void CompilerErrorAnalytics (int mode, const char *condition) +{ +#if UNITY_EDITOR + if ( mode & kScriptCompileError ) + { + std::string message = condition; + + // The compiler error message is formatted like this: + // filename: error/warning number: description + int n1 = message.find(": "); + if ( n1 != string::npos ) + { + int n2 = message.find(": ", n1+2); + if ( n2 != string::npos ) + { + string error = message.substr(n1+2, n2-n1-2); + string description = message.substr(n2+2); + AnalyticsTrackEvent("Compiler", error, description, 1); + return; + } + } + AnalyticsTrackEvent("Compiler", "Unknown", message, 1); + } +#endif +} + +bool DefaultCleanLogHandlerv (LogType logType, const char* log, va_list alist) +{ + va_list list; + va_copy (list, alist); + +#if UNITY_ANDROID // On Android we don't use a separate clean log-file, but instead we clean up the actual logcat +#define LOG_PRINTF(x, ...) __android_log_vprint(ANDROID_LOG_INFO, "Unity", __VA_ARGS__) +#define LOG_FLUSH(x) +#else +#define LOG_PRINTF vfprintf +#define LOG_FLUSH fflush +#endif + +LOG_PRINTF (gReproductionLogFile, log, list); +LOG_FLUSH( gReproductionLogFile ); + +#undef LOG_PRINTF +#undef LOG_FLUSH + + va_end (list); + + return true; +} + +void CleanLogHandler(LogType logType, const char* log, ...) +{ + if (gCleanLogEntryHandlers != NULL) + { + for (std::list<LogEntryHandler>::iterator it = gCleanLogEntryHandlers->begin(); + it != gCleanLogEntryHandlers->end(); + it++) + { + va_list vl; + va_start(vl, log); + (**it)(logType, log, vl); + } + } +} + + +typedef void PrintConsole (const char* log, ...); + +// convert the log mode to the cs LogType enum +// LogType.Error = 0, LogType.Assert = 1, LogType.Warning = 2, LogType.Log = 3, LogType.Exception = 4 +inline LogType LogModeToLogType(int mode) +{ + LogType logType; + if ( mode & (kScriptingException) ) logType = LogType_Exception; + else if ( mode & (kError | kFatal | kScriptingError | kScriptCompileError | kStickyError | kAssetImportError | kGraphCompileError) ) logType = LogType_Error; + else if ( mode & kAssert ) logType = LogType_Assert; + else if ( mode & (kScriptingWarning | kScriptCompileWarning | kAssetImportWarning) ) logType = LogType_Warning; + else logType = LogType_Log; + + return logType; +} + +void DebugStringToFilePostprocessedStacktrace (const char* condition, const char* strippedStacktrace, const char* stacktrace, int errorNum, const char* file, int line, int mode, int objectInstanceID, int identifier) +{ + LogType logType = LogModeToLogType(mode); + + #if !UNITY_EXTERNAL_TOOL + int depth = gRecursionLock; + if (depth == 1) + return; + gRecursionLock = 1; + + if ( gLogCallbackFunc) + { +#if SUPPORT_THREADS + if (gLogCallbackFuncThreadSafe || Thread::CurrentThreadIsMainThread()) +#endif + gLogCallbackFunc (condition, strippedStacktrace, (int)logType); + } + + #endif +#if UNITY_WII + if (mode & kFatal) + { + wii::DbgFatalError ("%s\n", condition); + } + else if (mode & (kAssert | kError)) + { + wii::DbgOutput ("%s\n", condition); + } +#elif UNITY_XENON && MASTER_BUILD + return; +#endif + CompilerErrorAnalytics (mode, condition); + + string conditionAndStacktrace = condition; + if ( stacktrace ) + { + conditionAndStacktrace += "\n"; + conditionAndStacktrace += stacktrace; + } + + string conditionAndStrippedStacktrace = condition; + if ( stacktrace ) + { + conditionAndStrippedStacktrace += "\n"; + conditionAndStrippedStacktrace += strippedStacktrace; + } + + if (errorNum) + CleanLogHandler (logType, "%s (Error: %d)\n\n", condition, errorNum); + else + CleanLogHandler (logType, "%s\n\n", condition); + + + PrintConsole* printConsole = InternalIgnoreConsole; + // Logs + if (mode & (kLog | kScriptingLog)) + printConsole = InternalLogConsole; + else if (mode & (kScriptingWarning | kAssetImportWarning)) + printConsole = InternalWarningConsole; + else if (mode & kAssert) + printConsole = InternalAssertConsole; + // Real errors ---- YOU WANT TO BREAKPOINT THIS LINE! + else + printConsole = InternalErrorConsole; + + + if (errorNum) + { + if (ContainsNewLine (conditionAndStacktrace.c_str())) + printConsole ("%s \n(Error: %li Filename: %s Line: %li)\n\n", conditionAndStacktrace.c_str(), errorNum, file, line); + else + printConsole ("%s (Error: %li Filename: %s Line: %li)\n", conditionAndStacktrace.c_str(), errorNum, file, line); + } + else + { + if (ContainsNewLine (conditionAndStacktrace.c_str())) + printConsole ("%s \n(Filename: %s Line: %li)\n\n", conditionAndStacktrace.c_str(), file, line); + else + printConsole ("%s (Filename: %s Line: %li)\n", conditionAndStacktrace.c_str(), file, line); + } + + if (gLogToConsoleFunc) + gLogToConsoleFunc (conditionAndStrippedStacktrace, errorNum, file, line, mode, objectInstanceID, identifier); + + #if WEBPLUG + #if DEBUGMODE && 0 + if (mode & kAssert) + { + DebugBreak(); + } + #endif + if (mode & kFatal) + { + ExitWithErrorCode(kErrorFatalException); + } + #elif UNITY_WINRT + if (mode & kAssert) + { + // Used to set a breakpoint + int s = 5; + } + if (mode & kFatal) + __debugbreak(); + #endif + + #if !UNITY_EXTERNAL_TOOL + gRecursionLock = 0; + #endif +} + +PROFILER_INFORMATION (gProfilerLogString, "LogStringToConsole", kProfilerOther); + +void DebugStringToFile (const char* condition, int errorNum, const char* file, int line, int mode, int objectInstanceID, int identifier) +{ + PROFILER_AUTO(gProfilerLogString, NULL); + SET_ALLOC_OWNER(NULL); +#if UNITY_ANDROID && i386 + printf_console("%s: %d at %s:%d (%d, %d, %d)\n", condition, errorNum, file, line, mode, objectInstanceID, identifier); +#endif + +#if UNITY_FLASH || UNITY_WEBGL + printf_console(condition); + if (!(mode&(kScriptingWarning | kScriptingLog ))){ +#if UNITY_FLASH + Ext_Flash_LogCallstack();//Let's only switch this on for internal debugging, it sucks having to go through callstacks because of a log. +#elif UNITY_WEBGL + // __asm __volatile__("console.log(new Error().stack)"); +#endif + }else{ + return; + } +#endif + + string stackTrace; + string strippedStackTrace; + string preprocessedFile; + if (gPreprocessor) + { + preprocessedFile = file; + string conditionStr = condition; + + gPreprocessor (conditionStr, strippedStackTrace, stackTrace, errorNum, preprocessedFile, &line, mode, objectInstanceID); + + file = preprocessedFile.c_str (); + } + + DebugStringToFilePostprocessedStacktrace (condition, strippedStackTrace.c_str(), stackTrace.c_str(), errorNum, file, line, mode, objectInstanceID, identifier); + +} + +void RemoveErrorWithIdentifierFromConsole (int identifier) +{ + if (gRemoveLog) + gRemoveLog (identifier); +} + +void ShowErrorWithMode (int identifier) +{ + if (gShowLogWithMode) + gShowLogWithMode (identifier); +} + + +void DebugTextLineByLine(const char* text, int maxLineLen) +{ + if(maxLineLen == -1) + maxLineLen = 1023; + + // on android we can output maximum 1023 chars (1024 including \0) +#if UNITY_ANDROID + if(maxLineLen > 1023) + maxLineLen = 1023; +#endif + + #define SKIP_CRLF(ptr) \ + do{ \ + while( (*ptr == '\r' || *ptr == '\n') && *ptr != 0 ) \ + ++ptr; \ + } while(0) + + #define SKIP_UNTIL_CRLF(ptr) \ + do{ \ + while( *ptr != '\r' && *ptr != '\n' && *ptr != 0 ) \ + ++ptr; \ + } while(0) + + + const char* lineStart = text; + SKIP_CRLF(lineStart); + + std::string out; + while(*lineStart != 0) + { + const char* lineEnd = lineStart; + SKIP_UNTIL_CRLF(lineEnd); + + if(lineEnd - lineStart > maxLineLen) + lineEnd = lineStart + maxLineLen; + + bool needSkipCRLF = *lineEnd == '\r' || *lineEnd == '\n'; + + out.assign(lineStart, lineEnd-lineStart); + #if UNITY_ANDROID + __android_log_print( ANDROID_LOG_DEBUG, "Unity", "%s", out.c_str()); + #else + printf_console("%s\n", out.c_str()); + #endif + + lineStart = lineEnd; + if(needSkipCRLF) + SKIP_CRLF(lineStart); + } + + #undef SKIP_UNTIL_CRLF + #undef SKIP_CRLF +} + + +#if UNITY_IPHONE || UNITY_ANDROID + +#if UNITY_IPHONE +#include <execinfo.h> +#elif UNITY_ANDROID +#include "PlatformDependent/AndroidPlayer/utils/backtrace_impl.h" +#endif + +void DumpCallstackConsole( const char* prefix, const char* file, int line ) +{ + const size_t kMaxDepth = 100; + + size_t stackDepth; + void* stackAddr[kMaxDepth]; + char** stackSymbol; + + stackDepth = backtrace(stackAddr, kMaxDepth); + stackSymbol = backtrace_symbols(stackAddr, stackDepth); + + printf_console("%s%s:%d\n", prefix, file, line); + + // TODO: demangle? use unwind to get more info? + // start from 1 to bypass self + for( unsigned stackI = 1 ; stackI < stackDepth ; ++stackI ) + { + #if UNITY_IPHONE + printf_console(" #%02d %s\n", stackI-1, stackSymbol[stackI]); + #elif UNITY_ANDROID // just for now + __android_log_print( ANDROID_LOG_DEBUG, "DEBUG", " #%02d %s\n", stackI-1, stackSymbol[stackI]); + #endif + } + + ::free(stackSymbol); +} + +#else + +void DumpCallstackConsole( const char* /*prefix*/, const char* /*file*/, int /*line*/ ) +{ +} + +#endif + +#if UNITY_OSX || UNITY_IPHONE +#include <sys/sysctl.h> +// taken from apple technical note QA1361 +bool EXPORT_COREMODULE IsDebuggerPresent() +{ + static bool debuggerPresent = false; + static bool inited = false; + + if(!inited) + { + kinfo_proc info; + ::memset(&info, 0x00, sizeof(info)); + + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, ::getpid()}; + + size_t size = sizeof(info); + sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + + debuggerPresent = (info.kp_proc.p_flag & P_TRACED) != 0; + inited = true; + } + + return debuggerPresent; +} +#endif + +#if UNITY_LINUX || UNITY_TIZEN +#include <sys/ptrace.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <errno.h> + +bool IsDebuggerPresent () +{ + int status = 0, + pid = -1, + result = 0; + +#ifdef PR_SET_PTRACER +// Guard ancient versions (&*^$%@*&#^%$ build agents) + // Enable tracing by self and children + if (0 != prctl (PR_SET_PTRACER, getpid (), 0, 0, 0)) + { + ErrorString (Format ("Unable to enable tracing: %s", strerror (errno))); + return false; + } +#endif + + pid = fork (); + if (0 > pid) + { + ErrorString ("Error creating child process"); + return false; + } + + if (0 == pid) + { + // Child + int parent = getppid(); + + // Attempt to attach to parent + if (ptrace (PTRACE_ATTACH, parent, NULL, NULL) == 0) + { + // Debugger is not attached; continue parent once it stops + waitpid (parent, NULL, WUNTRACED | WCONTINUED); + ptrace (PTRACE_DETACH, getppid (), NULL, NULL); + result = 0; + } + else + { + // Debugger is already tracing parent + result = 1; + } + exit(result); + } + + // Parent + waitpid (pid, &status, 0); + result = WEXITSTATUS (status); +#ifdef PR_SET_PTRACER + // Clear tracing + prctl (PR_SET_PTRACER, 0, 0, 0, 0); +#endif + + return (0 != result); +} +#endif + + diff --git a/Runtime/Utilities/LogAssert.h b/Runtime/Utilities/LogAssert.h new file mode 100644 index 0000000..f3cb304 --- /dev/null +++ b/Runtime/Utilities/LogAssert.h @@ -0,0 +1,340 @@ +#ifndef LOGASSERT_H +#define LOGASSERT_H + +class Object; +#include <string> +#include <set> +#include <stdarg.h> +#include <stdio.h> +#include "Annotations.h" +#include "Runtime/Utilities/FileStripped.h" +#if UNITY_EXTERNAL_TOOL +#define EXPORT_COREMODULE +#else +#include "Runtime/Modules/ExportModules.h" +#endif + +#if UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN + #include <signal.h> +#endif + +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX + extern bool IsDebuggerPresent(); +#endif + +enum +{ + kError = 1 << 0, + kAssert = 1 << 1, + kLog = 1 << 2, + kFatal = 1 << 4, + kAssetImportError = 1 << 6, + kAssetImportWarning = 1 << 7, + kScriptingError = 1 << 8, + kScriptingWarning = 1 << 9, + kScriptingLog = 1 << 10, + kScriptCompileError = 1 << 11, + kScriptCompileWarning = 1 << 12, + kStickyError = 1 << 13, + kMayIgnoreLineNumber = 1 << 14, + kReportBug = 1 << 15, + kDisplayPreviousErrorInStatusBar = 1 << 16, + kScriptingException = 1 << 17, + kDontExtractStacktrace = 1 << 18, + kGraphCompileError = 1 << 20, +}; + +/// The type of the log message in the delegate registered with Application.RegisterLogCallback. +/// +enum LogType +{ + /// LogType used for Errors. + LogType_Error = 0, + /// LogType used for Asserts. (These indicate an error inside Unity itself.) + LogType_Assert = 1, + /// LogType used for Warnings. + LogType_Warning = 2, + /// LogType used for regular log messages. + LogType_Log = 3, + /// LogType used for Exceptions. + LogType_Exception = 4, + /// LogType used for Debug. + LogType_Debug = 5, + /// + LogType_NumLevels +}; + +inline const char* LogTypeToString (LogType type) +{ + switch (type) + { + case LogType_Assert: return "Assert"; + case LogType_Debug: return "Debug"; + case LogType_Exception: return "Exception"; + case LogType_Error: return "Error"; + case LogType_Log: return "Log"; + case LogType_Warning: return "Warning"; + default: return ""; + } +} + +typedef void LogToConsoleImpl (const std::string& condition, int errorNum, const char* file, int line, int type, int targetObjectInstanceID, int identifier); +typedef void LogCallback (const std::string& condition, const std::string &stackTrace, int type); +typedef void PreprocessCondition (const std::string& condition, std::string &strippedStacktrace, std::string &stackTrace, int errorNum, std::string& file, int* line, int type, int targetInstanceID); +typedef void RemoveLogFunction (int identifier); + +/// Callback function that is invoked before a log message is written to the log output. Return true to +/// continue logging the message as normal or false to mark the log message as handled and prevent normal +/// processing of it. +typedef bool (*LogEntryHandler) (LogType logType, const char* log, va_list list); + +void RegisterLogToConsole (LogToConsoleImpl* func); +void RegisterLogCallback (LogCallback* callback, bool threadSafe); +void RegisterLogPreprocessor (PreprocessCondition* func); +void RegisterRemoveImportErrorFromConsole (RemoveLogFunction* func); +void RegisterShowErrorWithMode (RemoveLogFunction* func); + +void DebugStringToFilePostprocessedStacktrace (const char* condition, const char* strippedStacktrace, const char* stacktrace, int errorNum, const char* file, int line, int mode, int targetInstanceID = 0, int identifier = 0); +void EXPORT_COREMODULE DebugStringToFile (const char* condition, int errorNum, const char* file, int line, int mode, int targetInstanceID = 0, int identifier = 0); +template<typename alloc> +void EXPORT_COREMODULE DebugStringToFile (const std::basic_string<char, std::char_traits<char>, alloc>& condition, int errorNum, const char* file, int line, int mode, const int objectInstanceID = 0, int identifier = 0) +{ + DebugStringToFile (condition.c_str (), errorNum, file, line, mode, objectInstanceID, identifier); +} + +void SetLogEntryHandler(LogEntryHandler newHandler); +void AddCleanLogEntryHandler(LogEntryHandler newHandler); +void ReleaseLogHandlers(); + +void DumpCallstackConsole( const char* prefix, const char* file, int line ); +#define DUMP_CALLSTACK(message) DumpCallstackConsole(message, __FILE_STRIPPED__, __LINE__) + +void DebugTextLineByLine(const char* text, int maxLineLen=-1); + +#define ErrorIf(x) do{ if (x) DebugStringToFile (#x, 0, __FILE_STRIPPED__, __LINE__, kError); ANALYSIS_ASSUME(!(x)); }while(0) +#define ErrorAndReturnValueIf(x, y) do{ if (x) { DebugStringToFile (#x, 0, __FILE_STRIPPED__, __LINE__, kError); return y; ANALYSIS_ASSUME(!(x)); } else { ANALYSIS_ASSUME(!(x)); } }while(0) +#define ErrorAndReturnIf(x) ErrorAndReturnValueIf( x, /**/ ) + +#define ErrorString(x) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kError); }while(0) +#define ErrorStringWithoutStacktrace(x) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kDontExtractStacktrace | kError); }while(0) +#define ErrorStringMsg(...) do{ DebugStringToFile (Format(__VA_ARGS__), 0, __FILE_STRIPPED__, __LINE__, kError); }while(0) +#define WarningString(x) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kScriptingWarning); }while(0) +#define WarningStringWithoutStacktrace(x) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kDontExtractStacktrace | kScriptingWarning); }while(0) +#define WarningStringMsg(...) do{ DebugStringToFile (Format(__VA_ARGS__), 0, __FILE_STRIPPED__, __LINE__, kScriptingWarning); }while(0) + + +#define ErrorOSErr(x) do{ int XERRORRESULT = x; if (XERRORRESULT) DebugStringToFile (#x, XERRORRESULT, __FILE_STRIPPED__, __LINE__, kError); }while(0) + +/// These errors pass an Object* as the place in which object the error occurred +#define ErrorIfObject(x,o) do{ if (x) DebugStringToFile (#x, 0, __FILE_STRIPPED__, __LINE__, kError, (o) ? (o)->GetInstanceID() : 0); }while(0) +#define ErrorStringObject(x,o) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kError, (o) ? (o)->GetInstanceID() : 0); }while(0) +#define WarningStringObject(x,o) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kScriptingWarning, (o) ? (o)->GetInstanceID() : 0); }while(0) + +#define LogStringObject(x,o) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kLog, (o) ? (o)->GetInstanceID() : 0); }while(0) +#define LogString(x) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kLog); }while(0) +#define LogStringMsg(...) do{ DebugStringToFile (Format(__VA_ARGS__), 0, __FILE_STRIPPED__, __LINE__, kLog); }while(0) + +#define FatalErrorOSErr(x) do{ int XERRORRESULT = x; if (XERRORRESULT) DebugStringToFile (#x, XERRORRESULT, __FILE_STRIPPED__, __LINE__, kError | kFatal | kReportBug); }while(0) +#define FatalErrorString(x) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kError | kFatal | kReportBug); }while(0) +#define FatalErrorIf(x) do{ if (x) DebugStringToFile (#x, 0, __FILE_STRIPPED__, __LINE__, kError | kFatal | kReportBug); }while(0) +#define FatalErrorStringDontReport(x) do{ DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kError | kFatal); }while(0) +#define FatalErrorMsg(...) do{ DebugStringToFile (Format(__VA_ARGS__), 0, __FILE_STRIPPED__, __LINE__, kError | kFatal); }while(0) + + +#define ErrorFiniteParameter(x) if (!IsFinite(x)) { ErrorString("Invalid parameter because it was infinity or nan."); return; } +#define ErrorFiniteParameterReturnFalse(x) if (!IsFinite(x)) { ErrorString("Invalid parameter because it was infinity or nan."); return false; } + + +/***** MAKE SURE THAT AssertIf's only compare code to compare, ****/ +/***** Which can safely be not called in non-debug mode ****/ + + +// TODO: should have ASSERT_ENABLED define, but difficult to add now + +#if defined(__ppc__) + #define DEBUG_BREAK __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" : : : "memory","r0","r3","r4" ) +#elif UNITY_OSX + #define DEBUG_BREAK if(IsDebuggerPresent()) __asm { int 3 } +#elif UNITY_WIN + #define DEBUG_BREAK if (IsDebuggerPresent()) __debugbreak() +#elif UNITY_XENON || UNITY_METRO + #define DEBUG_BREAK __debugbreak() +#elif UNITY_PS3 + #define DEBUG_BREAK __asm__ volatile ("tw 31,1,1 ") +#elif (UNITY_IPHONE && !TARGET_IPHONE_SIMULATOR) + #define DEBUG_BREAK __asm__ __volatile__ ( "bkpt #0\n\t bx lr\n\t" : : : ) +#elif UNITY_ANDROID + #define DEBUG_BREAK __builtin_trap() +#elif UNITY_LINUX + #define DEBUG_BREAK if (IsDebuggerPresent ()) raise(SIGTRAP) +#else + #define DEBUG_BREAK +#endif + +#ifndef ASSERT_SHOULD_BREAK +// On Metro breaking without debugger attached stops the program... So don't do it ! +#define ASSERT_SHOULD_BREAK DEBUGMODE && !UNITY_RELEASE && !UNITY_METRO +#endif + +#if ASSERT_SHOULD_BREAK + #define ASSERT_BREAK DEBUG_BREAK +#else + #define ASSERT_BREAK +#endif + + +#if DEBUGMODE + + #define AssertIf(x) \ + do { \ + if(x) \ + { \ + DebugStringToFile (#x, 0, __FILE_STRIPPED__, __LINE__, kAssert); \ + ASSERT_BREAK; \ + ANALYSIS_ASSUME(!(x)); \ + } \ + } while(0) + + #define AssertIfObject(x,o) \ + do { \ + if(x) \ + { \ + DebugStringToFile (#x, 0, __FILE_STRIPPED__, __LINE__, kAssert, (o)?(o)->GetInstanceID():0); \ + ASSERT_BREAK; \ + ANALYSIS_ASSUME(!(x)); \ + } \ + } while(0) + + + #define Assert(x) \ + do { \ + if(!(x)) \ + { \ + DebugStringToFile (#x, 0, __FILE_STRIPPED__, __LINE__, kAssert); \ + ASSERT_BREAK; \ + ANALYSIS_ASSUME(x); \ + } \ + } while(0) + + #define AssertMsg(x,...) \ + do { \ + if(!(x)) \ + { \ + DebugStringToFile (Format(__VA_ARGS__), 0, __FILE_STRIPPED__, __LINE__, kAssert); \ + ASSERT_BREAK; \ + ANALYSIS_ASSUME(x); \ + } \ + } while(0) + + #define AssertMsgObject(x,o,...) \ + do { \ + if(!(x)) \ + { \ + DebugStringToFile (Format(__VA_ARGS__), 0, __FILE_STRIPPED__, __LINE__, kAssert, (o) ? (o)->GetInstanceID() : 0); \ + ASSERT_BREAK; \ + ANALYSIS_ASSUME(x); \ + } \ + } while(0) + + +#else + + #define AssertIf(x) do { (void)sizeof(x); } while(0) + #define AssertIfObject(x,o) do { (void)sizeof(x); (void)sizeof(o); } while(0) + #define Assert(x) do { (void)sizeof(x); } while(0) + #define AssertMsg(x,...) do { (void)sizeof(x); } while(0) + #define AssertMsgObject(x,o,...) do { (void)sizeof(x); } while(0) + +#endif + + +#if DEBUGMODE + + #define AssertString(x) { DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kAssert); } + #define AssertFiniteParameter(x) if (!IsFinite(x)) { AssertString("Invalid parameter because it was infinity or nan."); } + + /// These errors pass an Object* as the place in which object the error occurred + #define AssertStringObject(x,o) { DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kAssert, (o) ? (o)->GetInstanceID() : 0); } + #define ScriptWarning(x,o) { DebugStringToFile (x, 0, __FILE_STRIPPED__, __LINE__, kScriptingWarning); } +#else + + #define AssertString(x) { } + #define AssertFiniteParameter(x) {} + + #define AssertStringObject(x,o) { } + #define ScriptWarning(x,o) { } +#endif + + +#if UNITY_RELEASE + #define DebugAssertIf(x) do { (void)sizeof(x); } while(0) + #define DebugAssert(x) do { (void)sizeof(x); } while(0) + #define DebugAssertMsg(x, ...) { } + #define AssertBreak(x) Assert(x) +#else + #define DebugAssertIf(x) AssertIf(x) + #define DebugAssert(x) Assert(x) + #define DebugAssertMsg(x, ...) AssertMsg(x, __VA_ARGS__) + + #define AssertBreak(x) \ + do { \ + if(!(x)) \ + { \ + DEBUG_BREAK; \ + Assert(x); \ + } \ + } while(0) + #endif + + +#ifdef __cplusplus +extern "C" + { +#endif +EXPORT_COREMODULE TAKES_PRINTF_ARGS(1,2) void printf_console (const char* string, ...); +#ifdef __cplusplus + } +#endif +void printf_consolev (LogType logType, const char* log, va_list list); + +#if MASTER_BUILD +#define UNITY_TRACE(...) +#define UNITY_TRACEIF(condition, ...) +#else +#define UNITY_TRACE(...) printf_console(__VA_ARGS__) +#define UNITY_TRACEIF(condition, ...) if (condition) printf_console(__VA_ARGS__) +#endif + +/// When logging an error it can be passed an identifier. The identifier can be used to remove errors from the console later on. +/// Eg. when reimporting an asset all errors generated for that asset should be removed before importing! +void RemoveErrorWithIdentifierFromConsole (int identifier); +void ShowErrorWithMode (int mode); + +#if UNITY_OSX +void ResetStdout(); +#endif + +// Route input to a custom outputfile out instead of Player.log +void LogOutputToSpecificFile (const char* path); +void InitializeCleanedLogFile (FILE* file); +std::string GetCleanedLogFile(); +std::string GetConsoleLogPath(); + +#if UNITY_EDITOR +std::string GetEditorConsoleLogPath (); +std::string GetPlayerConsoleLogPath (); +std::string GetMonoDevelopLogPath (); +#endif + +#if UNITY_WIN +FILE* OpenConsole (); +std::string SetLogFilePath(std::string const& path); +#endif + +#if defined(_MSC_VER) + #define PRINTF_SIZET_FORMAT "Iu" +#else + #define PRINTF_SIZET_FORMAT "zu" +#endif + +#endif diff --git a/Runtime/Utilities/LogUtility.cpp b/Runtime/Utilities/LogUtility.cpp new file mode 100644 index 0000000..7b16094 --- /dev/null +++ b/Runtime/Utilities/LogUtility.cpp @@ -0,0 +1,4 @@ +#include "UnityPrefix.h" +#include "LogUtility.h" + +int NestedLogOutput::s_LogDepth = 0; diff --git a/Runtime/Utilities/LogUtility.h b/Runtime/Utilities/LogUtility.h new file mode 100644 index 0000000..164ea3f --- /dev/null +++ b/Runtime/Utilities/LogUtility.h @@ -0,0 +1,28 @@ +#pragma once + +#include "LogAssert.h" + +// Nested log with automatic indentation. NESTED_LOG(name,fmt,...) prints to the console +// and indents further log calls. Log indentation is decreased again when current scope ends. + +class NestedLogOutput +{ +public: + NestedLogOutput(const char* name, const std::string& msg) + { + printf_console("%s: %*c%s\n", name, s_LogDepth, ' ', msg.c_str()); + s_LogDepth += 4; + } + ~NestedLogOutput() + { + s_LogDepth -= 4; + } +private: + static int s_LogDepth; +}; + +#if !UNITY_RELEASE +#define NESTED_LOG(name,...) NestedLogOutput _nested_log_##__LINE__ (name,Format(__VA_ARGS__)) +#else +#define NESTED_LOG(name,...) +#endif diff --git a/Runtime/Utilities/MemoryPool.cpp b/Runtime/Utilities/MemoryPool.cpp new file mode 100644 index 0000000..f11d88a --- /dev/null +++ b/Runtime/Utilities/MemoryPool.cpp @@ -0,0 +1,210 @@ +#include "UnityPrefix.h" +#include "MemoryPool.h" +#include "Runtime/Utilities/Word.h" +#include "Runtime/Profiler/MemoryProfiler.h" +#include "Runtime/Utilities/InitializeAndCleanup.h" + +static int kMinBlockSize = sizeof(void*); +UNITY_VECTOR(kMemPoolAlloc,MemoryPool*)* MemoryPool::s_MemoryPools = NULL; +void MemoryPool::StaticInitialize() +{ + s_MemoryPools = UNITY_NEW(UNITY_VECTOR(kMemPoolAlloc,MemoryPool*),kMemPoolAlloc); +} + +void MemoryPool::StaticDestroy() +{ + for(size_t i = 0; i < s_MemoryPools->size(); i++) + UNITY_DELETE((*s_MemoryPools)[i],kMemPoolAlloc); + UNITY_DELETE(s_MemoryPools,kMemPoolAlloc); +} + +static RegisterRuntimeInitializeAndCleanup s_MemoryPoolCallbacks(MemoryPool::StaticInitialize, MemoryPool::StaticDestroy); + +void MemoryPool::RegisterStaticMemoryPool(MemoryPool* pool) +{ + s_MemoryPools->push_back(pool); +} + +MemoryPool::MemoryPool( bool threadCheck, const char* name, int blockSize, int hintSize, MemLabelId label ) +: m_AllocLabel(MemLabelId(label.label, GET_CURRENT_ALLOC_ROOT_HEADER())) +, m_Bubbles(MemLabelId(label.label, GET_CURRENT_ALLOC_ROOT_HEADER())) +#if DEBUGMODE +, m_PeakAllocCount(0) +, m_Name(name) +#endif +{ + #if ENABLE_THREAD_CHECK_IN_ALLOCS + m_ThreadCheck = threadCheck; + #endif + + if (blockSize < kMinBlockSize) + blockSize = kMinBlockSize; + m_BlockSize = blockSize; + + + m_BubbleSize = hintSize; + m_BlocksPerBubble = (m_BubbleSize - sizeof(Bubble) + 1) / blockSize; + + int usedBubbleSize = sizeof(Bubble) + m_BlocksPerBubble * blockSize - 1; + Assert(usedBubbleSize <= m_BubbleSize); + Assert(m_BubbleSize - usedBubbleSize <= m_BlockSize); + + Assert (m_BlocksPerBubble >= 128); + Assert(hintSize % 4096 == 0); + + m_AllocateMemoryAutomatically = true; + + Reset(); +} + +MemoryPool::~MemoryPool() +{ + #if !UNITY_EDITOR && DEBUGMODE + if (m_AllocCount > 0) + ErrorStringMsg( "Memory pool has %d unallocated objects: %s", m_AllocCount, m_Name ); // some stuff not deallocated? + #endif + DeallocateAll(); +} + +void MemoryPool::Reset() +{ + #if DEBUGMODE + m_AllocCount = 0; + #endif + m_HeadOfFreeList = NULL; +} + +void MemoryPool::DeallocateAll() +{ + Bubbles::iterator it, itEnd = m_Bubbles.end(); + for( it = m_Bubbles.begin(); it != itEnd; ++it ) + UNITY_FREE( m_AllocLabel, *it ); + m_Bubbles.clear(); + Reset(); +} + +void MemoryPool::PreallocateMemory (int size) +{ + bool temp = m_AllocateMemoryAutomatically; + m_AllocateMemoryAutomatically = true; + for (int i=0;i <= size / (m_BlocksPerBubble * m_BlockSize);i++) + { + AllocNewBubble(); + } + m_AllocateMemoryAutomatically = temp; +} + +void MemoryPool::AllocNewBubble( ) +{ + if (!m_AllocateMemoryAutomatically) + return; + + AssertIf (m_BlocksPerBubble == 1); // can't have 1 element per bubble + + Bubble *bubble = (Bubble*)UNITY_MALLOC( m_AllocLabel, m_BubbleSize ); + AssertIf( !bubble ); + + // put to bubble list + m_Bubbles.push_back( bubble ); + + // setup the free list inside a bubble + void* oldHeadOfFreeList = m_HeadOfFreeList; + m_HeadOfFreeList = bubble->data; + AssertIf( !m_HeadOfFreeList ); + + void **newBubble = (void**)m_HeadOfFreeList; + for( int j = 0; j < m_BlocksPerBubble-1; ++j ) + { + newBubble[0] = (char*)newBubble + m_BlockSize; + newBubble = (void**)newBubble[0]; + } + + newBubble[0] = oldHeadOfFreeList; // continue with existing free list (or terminate with NULL if no free elements) + + // still failure, error out + if( !m_HeadOfFreeList ) + { + ErrorString( "out of memory!" ); + } +} + +void* MemoryPool::Allocate() +{ + return Allocate( m_BlockSize ); +} + +void *MemoryPool::Allocate( size_t amount ) +{ +#if ENABLE_THREAD_CHECK_IN_ALLOCS + ErrorAndReturnValueIf(m_ThreadCheck && Thread::mainThreadId && !Thread::CurrentThreadIsMainThread(), NULL); +#endif + + + void *returnBlock; + + if( amount > (unsigned int)m_BlockSize ) { + ErrorString( Format("requested larger amount than block size! requested: %d, blocksize: %d", (unsigned)amount, (unsigned)m_BlockSize )); + return NULL; + } + + if( !m_HeadOfFreeList ) { + // allocate new bubble + AllocNewBubble(); + + // Can't allocate + if( m_HeadOfFreeList == NULL ) + return NULL; + } + + #if DEBUGMODE + ++m_AllocCount; + if( m_AllocCount > m_PeakAllocCount ) + m_PeakAllocCount = m_AllocCount; + #endif + + returnBlock = m_HeadOfFreeList; + + // move the pointer to the next block + m_HeadOfFreeList = *((void**)m_HeadOfFreeList); + + return returnBlock; +} + +void MemoryPool::Deallocate( void *mem_Block ) +{ +#if ENABLE_THREAD_CHECK_IN_ALLOCS + ErrorAndReturnIf(m_ThreadCheck && Thread::mainThreadId && !Thread::CurrentThreadIsMainThread()); +#endif + + if( !mem_Block ) // ignore NULL deletes + return; + + #if DEBUGMODE + // check to see if the memory is from the allocated range + bool ok = false; + size_t n = m_Bubbles.size(); + for( size_t i = 0; i < n; ++i ) { + Bubble* p = m_Bubbles[i]; + if( (char*)mem_Block >= p->data && (char*)mem_Block < (p->data + m_BlockSize * m_BlocksPerBubble) ) { + ok = true; + break; + } + } + AssertIf( !ok ); + #endif + + #if DEBUGMODE + // invalidate the memory + memset( mem_Block, 0xDD, m_BlockSize ); + AssertIf(m_AllocCount == 0); + #endif + + #if DEBUGMODE + --m_AllocCount; + #endif + + // make the block point to the first free item in the list + *((void**)mem_Block) = m_HeadOfFreeList; + // the list head is now the Deallocated block + m_HeadOfFreeList = mem_Block; +} diff --git a/Runtime/Utilities/MemoryPool.h b/Runtime/Utilities/MemoryPool.h new file mode 100644 index 0000000..b7ef488 --- /dev/null +++ b/Runtime/Utilities/MemoryPool.h @@ -0,0 +1,280 @@ +#ifndef MEMORY_POOL_H_ +#define MEMORY_POOL_H_ + +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Utilities/LogAssert.h" +#include "Runtime/Modules/ExportModules.h" + +#if ENABLE_THREAD_CHECK_IN_ALLOCS +#include "Runtime/Threads/Thread.h" +#endif + + +// -------------------------------------------------------------------------- +// A free-list based fixed size allocator. +// +// To override new/delete per class use DECLARE_POOLED_ALLOC and DEFINE_POOLED_ALLOC. +// +// memory_pool<T> is an STL allocator that only supports allocating a single element +// at a time. So it can be used with list, map, set but not with vector or string. +// +// Allocator creates "bubbles" of objects, each containin a free-list inside. When a bubble +// is full, it allocates a new one. Empty bubbles are NOT destroyed. + + +// -------------------------------------------------------------------------- + +class EXPORT_COREMODULE MemoryPool { +public: + MemoryPool( bool threadCheck, const char* name, int blockSize, int allocatedSizeHint, MemLabelId label = kMemPoolAlloc ); + ~MemoryPool(); + + /// Allocate single block + void* Allocate(); + /// Allocate less than single block + void* Allocate( size_t amount ); + /// Deallocate + void Deallocate( void *ptr ); + /// Deallocate everything + void DeallocateAll(); + + + #if !DEPLOY_OPTIMIZED + size_t GetBubbleCount() const { return m_Bubbles.size(); } + int GetAllocCount() const { return m_AllocCount; } + #endif + + int GetAllocatedBytes() { return m_Bubbles.size () * m_BlocksPerBubble * m_BlockSize; } + + #if DEBUGMODE + int GetAllocatedObjectsCount() { return m_AllocCount; } + #endif + + void PreallocateMemory(int size); + void SetAllocateMemoryAutomatically (bool allocateMemoryAuto) { m_AllocateMemoryAutomatically = allocateMemoryAuto; } + + static void StaticInitialize(); + static void StaticDestroy(); + static void RegisterStaticMemoryPool(MemoryPool* pool); + +private: + void AllocNewBubble(); + + struct Bubble + { + char data[1]; // actually byteCount + }; + typedef dynamic_array<Bubble*> Bubbles; + + void Reset(); +private: + int m_BlockSize; + int m_BubbleSize; + int m_BlocksPerBubble; + + Bubbles m_Bubbles; + + void* m_HeadOfFreeList; + bool m_AllocateMemoryAutomatically; + + MemLabelId m_AllocLabel; + + int m_AllocCount; // number of blocks currently allocated + #if DEBUGMODE + int m_PeakAllocCount; // stats + const char* m_Name; // for debugging + #endif + + #if ENABLE_THREAD_CHECK_IN_ALLOCS + bool m_ThreadCheck; + #endif + + static UNITY_VECTOR(kMemPoolAlloc,MemoryPool*)* s_MemoryPools; +}; + + +// -------------------------------------------------------------------------- +// Macros for class fixed-size pooled allocations: +// DECLARE_POOLED_ALLOC in the .h file, in a private section of a class, +// DEFINE_POOLED_ALLOC in the .cpp file + +#define ENABLE_MEMORY_POOL 1 + +#if ENABLE_MEMORY_POOL + +#define STATIC_INITIALIZE_POOL( _clazz ) _clazz::s_PoolAllocator = UNITY_NEW(MemoryPool, kMemPoolAlloc)(true, #_clazz, sizeof(_clazz), _clazz::s_PoolSize) +#define STATIC_DESTROY_POOL( _clazz ) UNITY_DELETE(_clazz::s_PoolAllocator, kMemPoolAlloc) + +#define DECLARE_POOLED_ALLOC( _clazz ) \ +public: \ + inline void* operator new( size_t size ) { return s_PoolAllocator->Allocate(size); } \ + inline void operator delete( void* p ) { s_PoolAllocator->Deallocate(p); } \ + static MemoryPool *s_PoolAllocator; \ + static int s_PoolSize; \ +private: + +#define DEFINE_POOLED_ALLOC( _clazz, _bubbleSize ) \ + MemoryPool* _clazz::s_PoolAllocator = NULL; \ + int _clazz::s_PoolSize = _bubbleSize; + +#else + +#define STATIC_INITIALIZE_POOL( _clazz ) +#define STATIC_DESTROY_POOL( _clazz ) +#define DECLARE_POOLED_ALLOC( _clazz ) +#define DEFINE_POOLED_ALLOC( _clazz, _bubbleSize ) + +#endif + + +// -------------------------------------------------------------------------- + +template<int SIZE> +struct memory_pool_impl +{ + struct AutoPoolWrapper + { + AutoPoolWrapper( int size) + { + SET_ALLOC_OWNER(NULL); + pool = UNITY_NEW(MemoryPool( true, "mempoolalloc", size, 32 * 1024, kMemPoolAlloc ), kMemPoolAlloc); + MemoryPool::RegisterStaticMemoryPool(pool); + } + ~AutoPoolWrapper() + { + } + MemoryPool* pool; + }; + static MemoryPool& get_pool () { + static AutoPoolWrapper pool( SIZE ); + return *(pool.pool); + } +}; + +/* + + THIS IS NOT THREAD SAFE. sharing of pools is by size, thus pools might randomly be shared from different threads. + +*/ + + + +template<typename T> +class memory_pool +{ +public: + typedef size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T value_type; + + template <class U> struct rebind { typedef memory_pool<U> other; }; + + memory_pool() { } + memory_pool( const memory_pool<T>& ) { } + template<class B> memory_pool(const memory_pool<B>&) { } // construct from a related allocator + template<class B> memory_pool<T>& operator=(const memory_pool<B>&) { return *this; } // assign from a related allocator + + ~memory_pool() throw() { } + + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } + size_type max_size() const throw() {return size_t(-1) / sizeof(value_type);} + void construct(pointer p, const T& val) { ::new((void*)p) T(val); } + void destroy(pointer p) { p->~T(); } + + pointer allocate(size_type n, std::allocator<void>::const_pointer /*hint*/ = 0) + { + if(n==1) + return reinterpret_cast<pointer>( memory_pool_impl<sizeof(T)>::get_pool ().Allocate(n * sizeof(T)) ); + else + return reinterpret_cast<pointer>(UNITY_MALLOC(kMemPoolAlloc,n*sizeof(T))); + } + + void deallocate(pointer p, size_type n) + { + if(n==1) + return memory_pool_impl<sizeof(T)>::get_pool ().Deallocate( p ); + else + return UNITY_FREE(kMemPoolAlloc,p); + } +}; + +template<typename T> +class memory_pool_explicit +{ +public: + typedef size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T value_type; + template <class U> struct rebind { typedef memory_pool_explicit<U> other; }; + + MemoryPool* m_Pool; + + memory_pool_explicit(MemoryPool& pool) { m_Pool = &pool; } + memory_pool_explicit() { m_Pool = NULL; } + memory_pool_explicit( const memory_pool_explicit<T>& b) { m_Pool = b.m_Pool; } + template<class B> memory_pool_explicit(const memory_pool_explicit<B>& b) { m_Pool = b.m_Pool; } // construct from a related allocator + template<class B> memory_pool_explicit<T>& operator=(const memory_pool_explicit<B>& b) { m_Pool = b.m_Pool; return *this; } // assign from a related allocator + + ~memory_pool_explicit() throw() { } + + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } + size_type max_size() const throw() {return size_t(-1) / sizeof(value_type);} + void construct(pointer p, const T& val) { ::new((void*)p) T(val); } + void destroy(pointer p) { p->~T(); } + + pointer allocate(size_type n, std::allocator<void>::const_pointer /*hint*/ = 0) + { + DebugAssertIf(n != 1); + AssertIf(m_Pool == NULL); + return reinterpret_cast<pointer>( m_Pool->Allocate(n * sizeof(T)) ); + } + + void deallocate(pointer p, size_type n) + { + DebugAssertIf(n != 1); + AssertIf(m_Pool == NULL); + m_Pool->Deallocate( p ); + } +}; + +template<typename A, typename B> +inline bool operator==( const memory_pool<A>&, const memory_pool<B>& ) +{ + // test for allocator equality (always true) + return true; +} + +template<typename A, typename B> +inline bool operator!=( const memory_pool<A>&, const memory_pool<B>& ) +{ + // test for allocator inequality (always false) + return false; +} + + +template<typename A, typename B> +inline bool operator==( const memory_pool_explicit<A>& lhs, const memory_pool_explicit<B>& rhs) +{ + // test for allocator equality (always true) + return lhs.m_Pool == rhs.m_Pool; +} + +template<typename A, typename B> +inline bool operator!=( const memory_pool_explicit<A>& lhs, const memory_pool_explicit<B>& rhs) +{ + // test for allocator inequality (always false) + return lhs.m_Pool != rhs.m_Pool; +} + +#endif diff --git a/Runtime/Utilities/MemoryUtilities.cpp b/Runtime/Utilities/MemoryUtilities.cpp new file mode 100644 index 0000000..b97ffad --- /dev/null +++ b/Runtime/Utilities/MemoryUtilities.cpp @@ -0,0 +1,15 @@ +#include "UnityPrefix.h" +#include "MemoryUtilities.h" + +void memset32 (void *dst, UInt32 value, UInt64 bytecount) +{ + UInt32 i; + for( i = 0; i < (bytecount & (~3)); i+=4 ) + { + *((UInt32*)((char*)dst + i)) = value; + } + for( ; i < bytecount; i++ ) + { + ((char*)dst)[i] = ((char*)&value)[i&4]; + } +} diff --git a/Runtime/Utilities/MemoryUtilities.h b/Runtime/Utilities/MemoryUtilities.h new file mode 100644 index 0000000..78c097e --- /dev/null +++ b/Runtime/Utilities/MemoryUtilities.h @@ -0,0 +1,3 @@ +#pragma once + +void memset32(void *dst, UInt32 value, UInt64 bytecount); diff --git a/Runtime/Utilities/NonCopyable.h b/Runtime/Utilities/NonCopyable.h new file mode 100644 index 0000000..e23357f --- /dev/null +++ b/Runtime/Utilities/NonCopyable.h @@ -0,0 +1,18 @@ +#ifndef NON_COPYABLE_H +#define NON_COPYABLE_H + +#ifndef EXPORT_COREMODULE +#define EXPORT_COREMODULE +#endif + +class EXPORT_COREMODULE NonCopyable +{ +public: + NonCopyable() {} + +private: + NonCopyable(const NonCopyable&); + NonCopyable& operator=(const NonCopyable&); +}; + +#endif diff --git a/Runtime/Utilities/OptimizationUtility.h b/Runtime/Utilities/OptimizationUtility.h new file mode 100644 index 0000000..5b69177 --- /dev/null +++ b/Runtime/Utilities/OptimizationUtility.h @@ -0,0 +1,20 @@ +#ifndef OPTIMIZATION_UTILITY_H +#define OPTIMIZATION_UTILITY_H 1 + + +// ALIGN_LOOP_OPTIMIZATION should be placed in heavy inner loops! +// for (int i=0;i<10000;i++) +// ALIGN_LOOP_OPTIMIZATION +// ... + +// On the Wii global function alignment is 4 for debug builds to save exe size +// as there are a lot of non-inlined functions. +// In that case having align 16 generates warnings, so we blank the define. +#if defined(__MWERKS__) && UNITY_RELEASE && !defined(_DEBUG) +#define ALIGN_LOOP_OPTIMIZATION asm {align 16} +#else +///@TODO: optimize this for gcc too +#define ALIGN_LOOP_OPTIMIZATION +#endif + +#endif diff --git a/Runtime/Utilities/PathNameUtility.cpp b/Runtime/Utilities/PathNameUtility.cpp new file mode 100644 index 0000000..d168c61 --- /dev/null +++ b/Runtime/Utilities/PathNameUtility.cpp @@ -0,0 +1,601 @@ +#include "UnityPrefix.h" +#include "PathNameUtility.h" +#include "Word.h" + +#include <algorithm> + +using namespace std; + +static void AppendPathNameImpl( const string& pathName, const string& append, char separator, std::string& res ) +{ + res.reserve (pathName.size () + append.size () + 1); + if (!pathName.empty () && !append.empty ()) + { + if (pathName[pathName.size () - 1] == separator) + { + if (append[0] == separator) + { + res += pathName; + res.append (append.begin () + 1, append.end ()); + return; + } + else + { + res += pathName; + res += append; + return; + } + } + else + { + if (append[0] == separator) + { + res += pathName; + res += append; + return; + } + else + { + res += pathName; + res += separator; + res += append; + return; + } + } + } + else if (pathName.empty ()) + res = append; + else + res = pathName; +} + +string AppendPathName (const string& pathName, const string& append) +{ + string res; + AppendPathNameImpl( pathName, append, kPathNameSeparator, res ); + return res; +} + +string PlatformAppendPathName (const string& pathName, const string& append) +{ + string res; + AppendPathNameImpl( pathName, append, kPlatformPathNameSeparator, res ); + return res; +} + + +string AppendPathNameExtension (const string& pathName, const string& extension) +{ + if (extension.empty ()) + return pathName; + + string newPathName; + newPathName.reserve (pathName.size () + 1 + extension.size ()); + newPathName.append (pathName); + newPathName.append ("."); + newPathName.append (extension); + return newPathName; +} + +string GetPathNameExtension (const string& pathName) +{ + return GetPathNameExtension (pathName.c_str(), pathName.size()); +} + +const char* GetPathNameExtension (const char* path, size_t length) +{ + for (size_t i=0;i<length;i++) + { + if (path[length - i - 1] == kPathNameSeparator) + return ""; + if (path[length - i - 1] == '.') + return path + length - i; + } + return ""; +} + +string DeletePathNameExtension (const string& pathName) +{ + string::size_type slash = pathName.rfind (kPathNameSeparator); + string::size_type dot = pathName.rfind ('.'); + + if (dot != string::npos) + { + if (slash == string::npos || dot > slash) + return string (&pathName[0], dot); + } + return pathName; +} + +vector<string> FindSeparatedPathComponents (char const* constPathName, size_t size, char separator) +{ + char const* current = constPathName, *end = constPathName + size; + vector<string> components; + + while (current != end) + { + char const* pos = std::find (current, end, separator); + + if (pos != current) + components.push_back (std::string (current, pos)); + + if (pos == end) + break; + + current = pos + 1; + } + + return components; +} + +string DeleteLastPathNameComponent (const string& pathName) +{ + string::size_type p = pathName.rfind (kPathNameSeparator); + if (p == string::npos) + return string (); + else + return string (&pathName[0], p); +} + +string PlatformDeleteLastPathNameComponent (const string& pathName) +{ + string::size_type p = pathName.rfind (kPlatformPathNameSeparator); + if (p == string::npos) + return string (); + else + return string (&pathName[0], p); +} + +string GetLastPathNameComponent (const string& pathName) +{ + return GetLastPathNameComponent(pathName.c_str(), pathName.size()); +} + +const char* GetLastPathNameComponent (const char* path, size_t length) +{ + for (size_t i=0;i<length;i++) + { + if (path[length - i - 1] == kPathNameSeparator) + return path + length - i; + } + return path; +} + +string PlatformGetLastPathNameComponent(const string& pathName) +{ + string::size_type p = pathName.rfind (kPlatformPathNameSeparator); + if (p == string::npos) + return pathName; + else + return string (&pathName[1 + p], pathName.size () - 1 - p); +} + +string DeleteFirstPathNameComponent (const string& pathName) +{ + string::size_type p = pathName.find (kPathNameSeparator); + if (p == string::npos) + return string (); + else + return string (&pathName[1 + p], pathName.size () - 1 - p); +} + +string StandardizePathName (const string& pathName) +{ + if (pathName.empty ()) + return pathName; + + // Remove initial / if not a // in the beginning + if (pathName[0] == kPathNameSeparator && pathName.size () > 1 && pathName[1] != kPathNameSeparator) + return string (&pathName[1], pathName.size () - 1); + else + return pathName; +} + +bool IsPathNameVisible (const string& path) +{ + if (path.empty()) + return false; + + string::size_type p = 0; + while (p < path.size ()) + { + if (path[p] == '.') + return false; + p = path.find (kPathNameSeparator, p); + if (p == string::npos) + return true; + p++; + } + return true; +} + + +void ConvertSeparatorsToUnity( char* pathName ) +{ + while( *pathName != '\0' ) { + if( *pathName == '\\' ) + *pathName = kPathNameSeparator; + ++pathName; + } +} + +void ConvertSeparatorsToPlatform( char* pathName ) +{ + if (kPathNameSeparator != kPlatformPathNameSeparator) { + while( *pathName != '\0' ) { + if( *pathName == kPathNameSeparator ) + *pathName = kPlatformPathNameSeparator; + ++pathName; + } + } +} + +void ConvertSeparatorsToPlatform( std::string& pathName ) +{ + if (kPathNameSeparator != kPlatformPathNameSeparator) { + std::string::iterator it = pathName.begin(), itEnd = pathName.end(); + while( it != itEnd ) { + if( *it == kPathNameSeparator ) + *it = kPlatformPathNameSeparator; + ++it; + } + } +} + +#if UNITY_EDITOR +const char* invalidFilenameChars = "/?<>\\:*|\""; + +const char* GetInvalidFilenameChars () +{ + return invalidFilenameChars; +} + +bool IsValidFileNameSymbol (const char c) +{ + return std::string(invalidFilenameChars).find(c) == string::npos; +} + +bool CheckValidFileName (const std::string& name) +{ + return CheckValidFileNameDetail(name) == kFileNameValid; +} + + +std::string MakeFileNameValid (const std::string& fileName) +{ + string output = fileName; + + // Disallow space at the beginning + while (!output.empty() && output[0] == ' ') + output.erase(0, 1); + + if (output.empty()) + return output; + + // Disallow space at the end + if (output[output.size()-1] == ' ' || output[output.size()-1] == '.') + output[output.size()-1] = '_'; + + for (int i=0;i<output.size();i++) + { + if (!IsValidFileNameSymbol(output[i])) + output[i] = '_'; + } + + + if (CheckValidFileName(output)) + return output; + else + return string(); +} + + +FileNameValid CheckValidFileNameDetail (const std::string& name) +{ + // none of these can be part of file/folder name + if( name.find_first_of("/\\") != string::npos ) + return kFileNameInvalid; + + // anything up to first dot can't be: com1..com9, lpt1..lpt9, con, nul, prn + size_t dotIndex = name.find('.'); + if( dotIndex == 4 ) + { + if( name[0]=='c' && name[1]=='o' && name[2]=='m' && name[3]>='0' && name[3]<='9' ) + return kFileNameInvalid; + if( name[0]=='l' && name[1]=='p' && name[2]=='t' && name[3]>='0' && name[3]<='9' ) + return kFileNameInvalid; + } + if( dotIndex == 3 ) + { + if( name[0]=='c' && name[1]=='o' && name[2]=='n' ) + return kFileNameInvalid; + if( name[0]=='n' && name[1]=='u' && name[2]=='l' ) + return kFileNameInvalid; + if( name[0]=='p' && name[1]=='r' && name[2]=='n' ) + return kFileNameInvalid; + } + + // . on OS X means invisible file + if( dotIndex == 0 ) + return kFileNameInvalid; + + // file/folder name can't end with a dot or a space + if( !name.empty() ) + { + // http://support.microsoft.com/kb/115827 + char lastChar = name[name.size()-1]; + if( lastChar == ' ' || lastChar == '.' ) + return kFileNameInvalid; + + // File names starting with a space will not get preserved correctly when zipping + // on windows. So don't allow that. + if (name[0] == ' ') + return kFileNameNotRecommended; + } + + if( name.find_first_of(invalidFilenameChars) != string::npos ) + return kFileNameNotRecommended; + + return kFileNameValid; +} + +std::string TrimSlash(const std::string& path) +{ + if (!path.empty()) + { + const char lastChar = path[path.size() - 1]; + if (lastChar == '/' || lastChar == '\\') + return path.substr(0, path.size() - 1); + } + + return path; +} + +string NormalizeUnicode (const string& utf8, bool decompose) +{ +#if UNITY_OSX + // OS X stores Unicode file names in decomposed form. Ie † (U-Umlaut), becomes represented as two characters + // -U and ¬ (Unicode U+0308), instead of a single † (Unicode U+00DC). For proper display and consistency of + // names, they need to be converted into precomposed form. + CFStringRef cfLowercased = StringToCFString(utf8); + if (!cfLowercased) + ErrorStringMsg("Failed to convert string '%s' to CFString", utf8.c_str()); + CFMutableStringRef cf = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cfLowercased); + CFStringNormalize(cf, decompose?kCFStringNormalizationFormD:kCFStringNormalizationFormC); + string result = CFStringToString(cf); + if (cf != NULL) + CFRelease(cf); + if (cfLowercased != NULL) + CFRelease(cfLowercased); + return result; +#else + return utf8; +#endif +} + +// Serialization does weird shit without case mangling - even if filesystem paths shouldn't +// be mangled on Linux, we apparently need them mangled in some serialization stuff anyway... +string& GetGoodPathNameForGUID (const string& pathname) +{ + static string lowercased; + + lowercased = GetGoodPathName(pathname); + +#if UNITY_LINUX + ToLowerInplace(lowercased); +#endif + + return lowercased; +} + +string& GetGoodPathName (const string& pathname) +{ + static string lowercased; + if (lowercased.capacity () < 4096) + lowercased.reserve (4096); + + if (!pathname.empty () && pathname[0] == kPathNameSeparator) + { + lowercased.clear(); + return lowercased; + } + + lowercased = pathname; + +#if UNITY_WIN + ConvertSeparatorsToUnity( lowercased ); +#endif + +#if UNITY_OSX + if (RequiresNormalization(lowercased)) + lowercased = NormalizeUnicode (lowercased, false); +#endif + +#if !UNITY_LINUX + ToLowerInplace(lowercased); +#endif + + return lowercased; +} + +#endif + + +#if !UNITY_PLUGIN +bool StartsWithPath (const std::string &s, const std::string& beginsWith) +{ + size_t beginsWithSize = beginsWith.size (); + if (s.size () < beginsWithSize) + return false; + + for (size_t i=0;i<beginsWithSize;i++) + { + if (ToLower(s[i]) != ToLower(beginsWith[i])) + return false; + } + + // Exact match or empty beginsWith + if (s.size () == beginsWithSize || beginsWith.empty()) + return true; + // Next unmatched character or last matched character is + // path separator. + else if (s[beginsWithSize] == kPathNameSeparator || + beginsWith[beginsWithSize - 1] == kPathNameSeparator) + return true; + else + return false; +} + +// Converts a hex digit to the actual value. Invalid hex characters are treated as '0' +static inline unsigned char Unhex(char c) { + if( c >= '0' && c <= '9') + return c-'0'; + if( c >= 'a' && c <= 'f') + return c-'a'+10; + if( c >= 'A' && c <= 'F') + return c-'A'+10; + return 0; +} + +// Takes a string containing path characters and URL escapes and returns a string that can be used as a file name +string EncodePath(const string& s) +{ + string result; + size_t i=0; + while( i < s.size() ) + { + size_t j=s.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ", i); + if (j == string::npos ) + { + result += ToLower(s.substr(i)); + break; + } + + if( j>i ) result += ToLower(s.substr(i,j-i)); + + if ( s[j] == '%' && j+2 < s.size() ) + { + // decode %xx URL escapes + unsigned char decoded = ToLower((char)(Unhex(s[j+1]) << 4 | Unhex(s[j+2]))); + if( (decoded >= 'a' && decoded <= 'z') || (decoded >= '0' && decoded <= '9') || decoded == ' ' ) + result += decoded; + else if (decoded == '/') + result += '-'; + else + result += Format("_%02x",(int)decoded); + i=j+3; + + } + else if (s[j] == '?') + { + // Don't include URL parameters + break; + } + else if (s[j] == '/') + { + // Convert slashes to dashes + while ( j+1 < s.size() && s[j+1] == '/' ) j++; // Collapse multiple /es into one + result += '-'; + i=j+1; + } + else + { + // Encode anything "dangerous" as _XX hex encoding + result += Format("_%02x",(int)s[j]); + i=j+1; + } + } + + return result; +} + +string DecodePath(const string& s) +{ + string result; + size_t i=0; + while( i < s.size() ) + { + size_t j=s.find("_", i); + if (j == string::npos ) + { + result += s.substr(i); + break; + } + + if( j>i ) result += s.substr (i,j-i); + + string escape = s.substr (j, 3); + unsigned int ch = ' '; + sscanf(escape.c_str(), "_%x", &ch); + + result += (unsigned char)ch; + i = j+3; + } + + return result; +} + +#endif + + + +// -------------------------------------------------------------------------- + +#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" + +SUITE (PathNameUtilityTests) +{ + #if UNITY_EDITOR + TEST(RequiresNormalization) + { + CHECK(!RequiresNormalization("")); + CHECK(!RequiresNormalization("!aA09~|")); + CHECK(RequiresNormalization("\n")); + CHECK(RequiresNormalization("\t")); + CHECK(RequiresNormalization("\x80")); + CHECK(RequiresNormalization("\xCC")); + } + + TEST(CheckValidFileName) + { + CHECK_EQUAL(kFileNameNotRecommended, CheckValidFileNameDetail(" temp")); + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("temp")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("temp.")); + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("tet.png.bytes")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("test/")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("test\\")); + CHECK_EQUAL(kFileNameNotRecommended, CheckValidFileNameDetail("te:st")); + + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("test...test.png")); + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("test..test.png")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("..")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("...")); + + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail(GetLastPathNameComponent("Assets/\\bad.tmp"))); + + CHECK_EQUAL("test", MakeFileNameValid("test")); + CHECK_EQUAL("", MakeFileNameValid(" ")); + CHECK_EQUAL("", MakeFileNameValid(" ")); + CHECK_EQUAL("", MakeFileNameValid("..test")); + CHECK_EQUAL("test", MakeFileNameValid(" test")); + CHECK_EQUAL("test_", MakeFileNameValid(" test ")); + CHECK_EQUAL("test_", MakeFileNameValid("test.")); + CHECK_EQUAL("____", MakeFileNameValid(" /// ")); + } + #endif + + TEST(GetPathNameExtension) + { + CHECK(strcmp(GetPathNameExtension(".dll").c_str(), "dll") == 0); + CHECK(strcmp(GetPathNameExtension(".dll/boing").c_str(), "") == 0); + CHECK(strcmp(GetPathNameExtension("hello/.dlL").c_str(), "dlL") == 0); + CHECK(strcmp(GetPathNameExtension("hello/blah.grr.dll").c_str(), "dll") == 0); + CHECK(strcmp(GetPathNameExtension("hello/boing.dll").c_str(), "dll") == 0); + } + + +} // SUITE + +#endif // ENABLE_UNIT_TESTS diff --git a/Runtime/Utilities/PathNameUtility.h b/Runtime/Utilities/PathNameUtility.h new file mode 100644 index 0000000..0ab1c48 --- /dev/null +++ b/Runtime/Utilities/PathNameUtility.h @@ -0,0 +1,153 @@ +#ifndef PATHNAMEUTILITY_H +#define PATHNAMEUTILITY_H + +#include <string> +#include <vector> +#include <functional> + + +// Path names in Unity always use forward slash as a separator; and convert to other one (if needed) +// only in low-level file functions. +const char kPathNameSeparator = '/'; + +// If absolutely required, this defines a platform specific path separator. +#if UNITY_WIN || UNITY_XENON +const char kPlatformPathNameSeparator = '\\'; +#elif UNITY_OSX || UNITY_WII || UNITY_PS3 || UNITY_IPHONE || UNITY_ANDROID || UNITY_PEPPER || UNITY_LINUX || UNITY_FLASH || UNITY_WEBGL || UNITY_BB10 || UNITY_TIZEN +const char kPlatformPathNameSeparator = '/'; +#else +#error "Unknown platform" +#endif + + + +std::string AppendPathName (const std::string& pathName, const std::string& append); +std::string AppendPathNameExtension (const std::string& pathName, const std::string& extension); + +std::string GetPathNameExtension (const std::string& pathName); +const char* GetPathNameExtension (const char* path, size_t cachedStringLength); +std::string GetLastPathNameComponent (const std::string& pathName); +const char* GetLastPathNameComponent (const char* path, size_t length); + + +// Returns true if path is a child folder of beginsWith or the path itself. Case insensitive. +bool StartsWithPath (const std::string &path, const std::string& beginsWith); + +std::vector<std::string> FindSeparatedPathComponents (char const* pathName, size_t size, char separator); +inline std::vector<std::string> FindSeparatedPathComponents (std::string const& pathName, char separator) { + return FindSeparatedPathComponents (pathName.c_str (), pathName.size (), separator); +} + +std::string DeletePathNameExtension (const std::string& pathName); +std::string StandardizePathName (const std::string& pathName); + +std::string DeleteLastPathNameComponent (const std::string& pathName); +std::string DeleteFirstPathNameComponent (const std::string& pathName); + +inline std::string GetFileNameWithoutExtension(const std::string& pathName) { return DeletePathNameExtension(GetLastPathNameComponent(pathName)); } + +bool IsPathNameVisible (const std::string& path); + + +void ConvertSeparatorsToUnity( char* pathName ); +template<typename alloc> +void ConvertSeparatorsToUnity( std::basic_string<char, std::char_traits<char>, alloc>& pathName ) +{ + typename std::basic_string<char, std::char_traits<char>, alloc>::iterator it = pathName.begin(), itEnd = pathName.end(); + while( it != itEnd ) { + if( *it == '\\' ) + *it = kPathNameSeparator; + ++it; + } +} +void ConvertSeparatorsToPlatform( char* pathName ); +void ConvertSeparatorsToPlatform( std::string& pathName ); + +int StrICmp (const char* a, const char* b); + +// These functions operate on platform dependent path separators + +std::string PlatformAppendPathName (const std::string& pathName, const std::string& append); +std::string PlatformGetLastPathNameComponent (const std::string& pathName); +std::string PlatformDeleteLastPathNameComponent (const std::string& pathName); + +std::string EncodePath(const std::string& s); +std::string DecodePath(const std::string& s); + +#if UNITY_EDITOR +bool IsValidFileNameSymbol (const char c); + +bool CheckValidFileName (const std::string& name); + +const char* GetInvalidFilenameChars (); + +enum FileNameValid { kFileNameValid = 0, kFileNameInvalid = 1, kFileNameNotRecommended = 2 }; +FileNameValid CheckValidFileNameDetail (const std::string& name); + +std::string MakeFileNameValid (const std::string& fileName); + +// Removes last / or \ if it exists in path +std::string TrimSlash(const std::string& path); + +std::string& GetGoodPathNameForGUID (const std::string& pathname); +std::string& GetGoodPathName (const std::string& pathname); + +std::string NormalizeUnicode (const std::string& utf8, bool decompose); + +inline bool RequiresNormalization (const char* utf8) +{ + const unsigned char* c = (const unsigned char*)utf8; + while (*c) + { + if (*c < 32 || *c > 127) + return true; + + c++; + } + + return false; +} + +inline bool RequiresNormalization (const std::string& utf8) +{ + return RequiresNormalization (utf8.c_str()); +} + +// Use this functor as a comparison function when using paths as keys in std::map. +struct PathCompareFunctor : std::binary_function<std::string, std::string, bool> +{ + bool operator() ( const std::string& path_a, const std::string& path_b ) const + { + #if UNITY_OSX + bool ascii = !RequiresNormalization (path_a) && !RequiresNormalization (path_b); + if (ascii) + return StrICmp(path_a.c_str(), path_b.c_str()) < 0; + else + return StrICmp(NormalizeUnicode(path_a, true).c_str(), NormalizeUnicode(path_b, true).c_str()) < 0; + #else + return StrICmp(path_a.c_str(), path_b.c_str()) < 0; + #endif + } +}; + +struct PathEqualityFunctor : std::binary_function<std::string, std::string, bool> +{ + bool operator() ( const std::string& path_a, const std::string& path_b ) const + { +#if UNITY_OSX + bool ascii = !RequiresNormalization (path_a) && !RequiresNormalization (path_b); + if (ascii) + return StrICmp(path_a.c_str(), path_b.c_str()) == 0; + else + return StrICmp(NormalizeUnicode(path_a, true).c_str(), NormalizeUnicode(path_b, true).c_str()) == 0; +#else + return StrICmp(path_a.c_str(), path_b.c_str()) == 0; +#endif + } +}; + + +#endif + +#endif + diff --git a/Runtime/Utilities/PlayerPrefs.h b/Runtime/Utilities/PlayerPrefs.h new file mode 100644 index 0000000..fbb693c --- /dev/null +++ b/Runtime/Utilities/PlayerPrefs.h @@ -0,0 +1,65 @@ +#ifndef PLAYERPREFS_H +#define PLAYERPREFS_H + +#include <string> +#include <vector> + +#if UNITY_EDITOR +class EditorPrefs { +public: + + static void UseCleanTestPrefs (); + static bool SetInt (const std::string& name, int value); + static bool SetBool (const std::string& name, bool value); + static bool SetString (const std::string& name, const std::string& value); + static bool SetFloat (const std::string& name, float value); + + static int GetInt (const std::string& name, int def = 0); + static bool GetBool (const std::string& name, bool def = false); + static std::string GetString (const std::string& name, const std::string& def = std::string ()); + static float GetFloat (const std::string& name, float def = 0.0F); + + static bool HasKey (const std::string& name); + static void DeleteKey (const std::string& name); + static void DeleteAll (); + + static void Sync (); +}; +#endif + +class PlayerPrefs { +public: + typedef std::vector<UInt8> RawData; +public: + static bool SetInt (const std::string& name, int value); + static bool SetString (const std::string& name, const std::string& value); + static bool SetFloat (const std::string& name, float value); + + static int GetInt (const std::string& name, int def = 0); + static std::string GetString (const std::string& name, const std::string& def = std::string ()); + static float GetFloat (const std::string& name, float def = 0.0F); + + static bool HasKey (const std::string& name); + static void DeleteKey (const std::string& name); + static void DeleteAll (); + + #if UNITY_WII_API && (UNITY_WIN || UNITY_WII) + static bool GetRawData(RawData& rawData); + static bool SetRawData(const RawData& rawData); + #endif + + static void Sync (); + #if UNITY_METRO + static void Init(); + #elif WEBPLUG + static void Init (const std::string& playerURL); + #endif + #if UNITY_LINUX + static std::string GetPath (); + #endif + + static void StaticInitialize(); + static void StaticDestroy(); +}; + +#endif diff --git a/Runtime/Utilities/Prefetch.h b/Runtime/Utilities/Prefetch.h new file mode 100644 index 0000000..d18922f --- /dev/null +++ b/Runtime/Utilities/Prefetch.h @@ -0,0 +1,52 @@ +#ifndef PREFETCH_H +#define PREFETCH_H + +#if UNITY_PS3 +#include <ppu_intrinsics.h> +#endif + +// This assembly will not work for Jungle. TODO: use arm version check here +#if defined(__arm__) && !UNITY_LINUX && !UNITY_WINRT + +inline void Prefetch(const void* p) +{ + unsigned char* pCurr = (unsigned char*)p; + asm volatile( + "pld [%0] \n\t" + : "=r" (pCurr) + : "0" (pCurr) + : "r0"); +} + +inline void Prefetch(const void* p, size_t size) +{ + unsigned char* pCurr = (unsigned char*)p; + unsigned char* pEnd = pCurr + size; + + while (pCurr < pEnd) + { + asm volatile( + "pld [%0] \n\t" + : "=r" (pCurr) + : "0" (pCurr) + : "r0"); + pCurr += 32; + } +} + +#elif defined(_XBOX) +__forceinline void Prefetch(const void* p, size_t size = 32) +{ + __dcbt(0, p); +} +#elif UNITY_PS3 +inline void Prefetch(const void* p, size_t size = 32) +{ + __dcbt(p); +} +#else +//@TODO: gcc __builtin_prefetch(p); profile & enable +inline void Prefetch(const void* /*p*/, size_t /*size*/ = 32) { } +#endif + +#endif diff --git a/Runtime/Utilities/RecursionLimit.h b/Runtime/Utilities/RecursionLimit.h new file mode 100644 index 0000000..8425335 --- /dev/null +++ b/Runtime/Utilities/RecursionLimit.h @@ -0,0 +1,54 @@ +#ifndef RECURSIONLIMIT_H +#define RECURSIONLIMIT_H + +class RecursionLimiter { +public: + RecursionLimiter (unsigned int *count) + { + m_Count = count; + (*m_Count)++; + } + + ~RecursionLimiter () + { + (*m_Count)--; + } +private: + unsigned int *m_Count; +}; + +#define LIMIT_RECURSION(x, retval) static unsigned int reclimit = 0; RecursionLimiter limiter(&reclimit); if (reclimit > x) return retval; + + +class ReentrancyChecker +{ +public: + ReentrancyChecker( bool* variable ) + : m_Variable(variable) + { + if( *m_Variable == false ) + { + *m_Variable = true; + m_OK = true; + } + else + { + m_OK = false; + } + } + ~ReentrancyChecker() + { + if( m_OK ) + { + *m_Variable = false; + } + } + bool IsOK() const { return m_OK; } + +private: + bool* m_Variable; + bool m_OK; +}; + + +#endif diff --git a/Runtime/Utilities/ReportHardware.cpp b/Runtime/Utilities/ReportHardware.cpp new file mode 100644 index 0000000..868287f --- /dev/null +++ b/Runtime/Utilities/ReportHardware.cpp @@ -0,0 +1,391 @@ +#include "UnityPrefix.h" +#include "ReportHardware.h" +#include "Runtime/Misc/SystemInfo.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "HashFunctions.h" +#include "Configuration/UnityConfigureVersion.h" +#include "Configuration/UnityConfigureOther.h" +#include "Configuration/UnityConfigure.h" +#include "Runtime/Graphics/ScreenManager.h" +#include "Runtime/Export/WWW.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Misc/CPUInfo.h" +#include "Runtime/Misc/PlayerSettings.h" +#include "Runtime/Utilities/GlobalPreferences.h" +#include "Runtime/Utilities/Argv.h" +#include "Runtime/Utilities/Word.h" + +#if UNITY_LINUX +#include "PlatformDependent/Linux/X11Quarantine.h" +#endif + +#if SUPPORT_REPRODUCE_LOG +#include "Runtime/Misc/ReproductionLog.h" +#endif + +#if UNITY_ANDROID +#include "PlatformDependent/AndroidPlayer/AndroidSystemInfo.h" +#include "PlatformDependent/AndroidPlayer/EntryPoint.h" +#endif + +#if UNITY_IPHONE + extern "C" const char* UnityAdvertisingIdentifier(); + extern "C" bool UnityAdvertisingTrackingEnabled(); +#endif + +#if UNITY_METRO +#include "PlatformDependent/MetroPlayer/AppCallbacks.h" +#include "PlatformDependent/MetroPlayer/MetroCapabilities.h" +#endif + +#if UNITY_OSX +static int numberForKey( CFDictionaryRef desc, CFStringRef key ) +{ + CFNumberRef value; + int num = 0; + if ( (value = (CFNumberRef)CFDictionaryGetValue(desc, key)) == NULL ) + return 0; + CFNumberGetValue(value, kCFNumberIntType, &num); + return num; +} +#endif + +#if UNITY_EDITOR +const char* kHardwareReportDoneKey = "Editor StatsDone"; +#elif UNITY_STANDALONE +const char* kHardwareReportDoneKey = "StandaloneStatsDone"; +#else +const char* kHardwareReportDoneKey = "StatsDone"; +#endif + +// URL to report hardware info to +const char* kHardwareReportURL = "http://stats.unity3d.com/HWStats.cgi"; + + +static bool CheckHardwareReportNeeded() +{ + #if !UNITY_ENABLE_HWSTATS_REPORT + return false; + #endif + + #if SUPPORT_REPRODUCE_LOG + if (RunningReproduction()) + return false; + #endif + + #if !WEBPLUG + if (!IsHumanControllingUs()) + return false; + #endif + + #if UNITY_ANDROID + // development player == no stats + if (UNITY_DEVELOPER_BUILD) + return false; + + if (!GetPlayerSettings().GetEnableHWStatistics()) + return false; + + // Only report once per OS version + std::string osName = systeminfo::GetOperatingSystem(); + UInt8 md5Value[16]; + ComputeMD5Hash( reinterpret_cast<const UInt8*>( osName.c_str() ), osName.size(), md5Value ); + std::string md5String = BytesToHexString( md5Value, 16 ); + + if (GetGlobalPreference(UNITY_VERSION_DIGITS) == md5String) + return false; + + SetGlobalPreference(UNITY_VERSION_DIGITS, md5String); + + return true; + #endif // #if UNITY_ANDROID + + + #if UNITY_METRO + if (metro::Capabilities::IsSupported(metro::Capabilities::kInternetClient, "", false) == false && + metro::Capabilities::IsSupported(metro::Capabilities::kInternetClientServer, "", false) == false) + return false; + #endif // #if UNITY_METRO + + #if UNITY_IPHONE || UNITY_BB10 || UNITY_TIZEN + if (!GetPlayerSettings().GetEnableHWStatistics()) + return false; + #endif + + if ( GetGlobalBoolPreference (kHardwareReportDoneKey, false) ) + return false; + SetGlobalBoolPreference (kHardwareReportDoneKey, true); + + return true; +} + + +static std::string EscapeFieldString( const std::string& s ) +{ + const char* kHexToLiteral = "0123456789abcdef"; + const char *kForbidden = "@&;:<>=?\"'/\\!#%+"; + + std::string result; + result.reserve( s.size() ); + for( size_t i = 0; i < s.size(); ++i ) { + unsigned char c = s[i]; + if( c == 32 ) + result += '+'; + else if( c < 32 || c > 126 || strchr(kForbidden, c) != NULL ) + { + result += '%'; + result += kHexToLiteral[c >> 4]; + result += kHexToLiteral[c & 0xF]; + } + else + result += c; + } + return result; +} + + +void HardwareInfoReporter::Shutdown() +{ + #if ENABLE_WWW + if( m_InfoPost != NULL ) + { + m_InfoPost->Release(); + m_InfoPost = NULL; + } + #endif +} + +static void StripGfxDriverVersion(std::string& fullName, const std::string& version) +{ + size_t pos = fullName.find(version); + + if (pos != std::string::npos && pos > 1) + fullName.erase(pos-1, version.size()+1); +} + +void HardwareInfoReporter::ReportHardwareInfo() +{ +#if ENABLE_WWW + AssertIf( m_InfoPost != NULL ); + + // If hardware info already reported, do nothing + if( !CheckHardwareReportNeeded() ) + return; + + + // Get all the information to post + std::string infoUUID; // Used to be sent by 2.0.2, not anymore with 2.1. Keeping to match the hash value. Now used by Android and iPhone only. + std::string deviceModel; // Used by Android and iPhone only + // Report runtime version used + std::string infoOS = systeminfo::GetOperatingSystem(); + std::string infoCPU = systeminfo::GetProcessorType(); + std::string infoGfxName = gGraphicsCaps.rendererString; + std::string infoGfxVendor = gGraphicsCaps.vendorString; + std::string infoGfxVersion = gGraphicsCaps.fixedVersionString; + std::string infoGfxDriver = gGraphicsCaps.driverLibraryString; +#if UNITY_ANDROID || UNITY_IPHONE || UNITY_BB10 || UNITY_TIZEN + std::string appId = GetPlayerSettings().GetiPhoneBundleIdentifier(); +#else + std::string appId = Format("%s.%s",GetPlayerSettings().GetCompanyName().c_str(), GetPlayerSettings().GetProductName().c_str()); +#endif + int infoCpuCount = systeminfo::GetProcessorCount(); + int infoRAM = systeminfo::GetPhysicalMemoryMB(); + int infoVRAM = gGraphicsCaps.videoMemoryMB; + + std::string unityVersion = UNITY_VERSION; // Report runtime version used + std::string buildVersion = "0.0.0"; // Unknown build version used + + // Get desktop display mode dimensions. Do this directly because + // we initiate the report before screen manager is even set up + // (so that in case something horrible happens while initializing + // the game, we'd still get a chance to get the report). + #if UNITY_WIN && !UNITY_WINRT + DEVMODE displayMode; + memset( &displayMode, 0, sizeof(displayMode) ); + displayMode.dmSize = sizeof(DEVMODE); + EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &displayMode ); + std::string infoScreenRes = Format("%i x %i", displayMode.dmPelsWidth, displayMode.dmPelsHeight); + + #elif UNITY_OSX + CFDictionaryRef currentMode = CGDisplayCurrentMode(kCGDirectMainDisplay); + int desktopWidth = numberForKey(currentMode, kCGDisplayWidth); + int desktopHeight = numberForKey(currentMode, kCGDisplayHeight); + std::string infoScreenRes = Format("%i x %i", desktopWidth, desktopHeight); + + #elif UNITY_LINUX + unsigned width = 0; + unsigned height = 0; + #if SUPPORT_X11 + NativeDisplayPtr display = OpenDisplay(NULL); + NativeWindow rootWindow = GetRootWindow(display); + GetWindowSize(display, rootWindow, &width, &height); + CloseDisplay(display); + #endif + std::string infoScreenRes = Format("%u x %u", width, height); + #elif UNITY_ANDROID + // Create a less obscure OS string + infoOS = Format ("%s API-%i", systeminfo::GetDeviceSystemName(), android::systeminfo::ApiLevel()); + // Strip 'driverLibraryString' from 'fixedVersionString', if duplicated + StripGfxDriverVersion(infoGfxVersion, infoGfxDriver); + // Get the raw display size (physical size of the display) + int width, height; + android::systeminfo::DisplaySize(&width, &height); + std::string infoScreenRes = Format("%u x %u", width, height); + // Use ANDROID ID/IMEI/WiFi MAC as UUID + infoUUID = android::systeminfo::UniqueIdentifier(); + // This matches the first part of ro.build.fingerprint + deviceModel = Format ("%s/%s/%s", android::systeminfo::Manufacturer(), android::systeminfo::Model(), android::systeminfo::Device()); + #elif UNITY_IPHONE + std::string infoScreenRes = Format("%u x %u", GetScreenManager().GetWidth(), GetScreenManager().GetHeight()); + infoUUID = systeminfo::GetDeviceUniqueIdentifier(); // it is hash of WiFi MAC address + deviceModel = systeminfo::GetDeviceModel(); + StripGfxDriverVersion(infoGfxVersion, infoGfxDriver); + #elif UNITY_BLACKBERRY + std::string infoScreenRes = Format("%u x %u", GetScreenManager().GetWidth(), GetScreenManager().GetHeight()); + // This will be EMPTY for Blackberry if permission is set false + infoUUID = systeminfo::GetDeviceUniqueIdentifier(); // Unique ID from OS / it is hash of WiFi MAC address + deviceModel = Format("%s/%s", "BlackBerry", systeminfo::GetDeviceModel()); + StripGfxDriverVersion(infoGfxVersion, infoGfxDriver); + #elif UNITY_METRO + std::string infoScreenRes = Format("%u x %u", GetScreenManager().GetWidth(), GetScreenManager().GetHeight()); + // It seems if we call GetDeviceUniqueIdentifier, this somehow distrubts the data flow, and server doesn't accept it + infoUUID = systeminfo::GetDeviceUniqueIdentifier(); // it is hash of WiFi MAC address + { + // Make infoUUID different one from the Editor and encode it again + infoUUID+="Metro"; + UInt8 md5Value[16]; + ComputeMD5Hash( reinterpret_cast<const UInt8*>(infoUUID.c_str() ), infoUUID.size(), md5Value); + infoUUID = BytesToHexString(md5Value, 16); + } + deviceModel = "n/a"; + #elif UNITY_WP8 + auto& screenManager = GetScreenManager (); + std::string infoScreenRes; + if (screenManager.GetScreenOrientation () == ScreenOrientation::kLandscapeLeft || screenManager.GetScreenOrientation () == ScreenOrientation::kLandscapeRight) + { + infoScreenRes = Format ("%u x %u", screenManager.GetHeight (), screenManager.GetWidth ()); + } + else + { + infoScreenRes = Format ("%u x %u", screenManager.GetWidth (), screenManager.GetHeight ()); + } + infoUUID = systeminfo::GetDeviceUniqueIdentifier (); + deviceModel = systeminfo::GetDeviceName (); + #elif UNITY_TIZEN + std::string infoScreenRes = Format("%u x %u", GetScreenManager().GetWidth(), GetScreenManager().GetHeight()); + infoUUID = systeminfo::GetDeviceUniqueIdentifier(); + deviceModel = Format("%s/%s", "Tizen", systeminfo::GetDeviceModel()); + StripGfxDriverVersion(infoGfxVersion, infoGfxDriver); + #else + #error "Unsupported platform" + #endif + + // Various flags, bits: + // 0..3: license type + // 4..5: metro presentation types + // 6..17: CPU capabilities, reserved up to 23 + // 24..25: GPU capabilities + int flags = 0; + BuildSettings* buildSettings = &GetBuildSettings(); + if (buildSettings) + { + // bits 0..2: 1=Indie, 2=Pro + flags |= buildSettings->hasPROVersion ? 2 : 1; + // bit 3: 0=Licensed, 1=Trial + if (!buildSettings->hasPublishingRights) + flags |= 8; + + // Report build version used + buildVersion = buildSettings->GetVersion(); + } + +#if UNITY_METRO + // bits 4..5: 1=XAML app, 2=D3D app + flags |= UnityPlayer::AppCallbacks::Instance->GetAppType() == UnityPlayer::AppCallbacks::kAppTypeD3DXAML ? (1 << 4) : (1 << 5); +#endif + + // CPU capabilities flags, bits 6..17 + flags |= (1<<6); // CPU caps bits are present (to determine between earlier versions that did not send these flags at all) + if (CPUInfo::HasSSE2Support()) + flags |= (1<<7); + if (CPUInfo::HasSSE41Support()) + flags |= (1<<8); + if (CPUInfo::HasSSE42Support()) + flags |= (1<<9); + if (CPUInfo::HasAVXSupport()) + flags |= (1<<10); + if (CPUInfo::HasNEONSupport()) + flags |= (1<<11); + if (CPUInfo::HasSSE3Support()) + flags |= (1<<12); + if (CPUInfo::HasSupplementalSSE3Support()) + flags |= (1<<13); + if (CPUInfo::HasAVX2Support()) + flags |= (1<<14); + if (CPUInfo::HasAVX512Support()) + flags |= (1<<15); + if (CPUInfo::HasFP16CSupport()) + flags |= (1<<16); + if (CPUInfo::HasFMASupport()) + flags |= (1<<17); + + // GPU capabilities flags, bits 24..25 + if (gGraphicsCaps.hasNativeDepthTexture) + flags |= (1<<24); + if (gGraphicsCaps.hasNativeShadowMap) + flags |= (1<<25); + + + // Send a hash some fields plus a salt as a cheap way to prevent "spam" on hardware + // report script. + std::string stringToHash; + stringToHash = std::string("KonfiguracijosReportoDruska-") + infoUUID + infoOS + infoCPU + infoGfxName + infoGfxVendor + infoGfxVersion; + + UInt8 md5Value[16]; + ComputeMD5Hash( reinterpret_cast<const UInt8*>( stringToHash.c_str() ), stringToHash.size(), md5Value ); + std::string md5String = BytesToHexString( md5Value, 16 ); + + // Construct POST request data + std::string postData; + postData += "os=" + EscapeFieldString(infoOS); + postData += "&cpu=" + EscapeFieldString(infoCPU); + postData += "&gfxname=" + EscapeFieldString(infoGfxName); + postData += "&gfxvendor=" + EscapeFieldString(infoGfxVendor); + postData += "&gfxversion=" + EscapeFieldString(infoGfxVersion); + postData += "&gfxdriver=" + EscapeFieldString(infoGfxDriver); + postData += "&cpucount=" + IntToString(infoCpuCount); + postData += "&ram=" + IntToString(infoRAM); + postData += "&vram=" + IntToString(infoVRAM); + postData += "&screen=" + EscapeFieldString(infoScreenRes); + postData += "&platform=" + IntToString(systeminfo::GetRuntimePlatform()); + postData += "&flags=" + IntToString(flags); + postData += "&hash=" + md5String; + postData += "&appId=" + EscapeFieldString(appId); +#if UNITY_ANDROID || UNITY_IPHONE || UNITY_BB10 || UNITY_WINRT || UNITY_TIZEN + postData += "&uuid=" + infoUUID; + postData += "&model=" + deviceModel; +#endif +#if UNITY_IPHONE + if (const char *iOSAdvertisingIdentifier = UnityAdvertisingIdentifier()) + postData += "&adid=" + std::string(iOSAdvertisingIdentifier); + postData += "&adtrack=" + std::string(UnityAdvertisingTrackingEnabled()?"1":"0"); +#endif + postData += "&unity=" + unityVersion; + postData += "&build=" + buildVersion; + + // Initiate POST request + std::map<std::string,std::string> headers; + headers.insert( std::make_pair("Content-Type", "application/x-www-form-urlencoded") ); + + m_InfoPost = WWW::Create( kHardwareReportURL, postData.c_str(), postData.size(), headers, false ); + + /* + // Server accepts only 5% percent of data, so if you want to debug, use the loop below, to ensure that data reaches server + for (int i = 0; i < 100; i++) + { + m_InfoPost = WWW::Create( kHardwareReportURL, postData.c_str(), postData.size(), headers, false ); + Thread::Sleep(0.1f); + } + */ +#endif // ENABLE_WWW +} diff --git a/Runtime/Utilities/ReportHardware.h b/Runtime/Utilities/ReportHardware.h new file mode 100644 index 0000000..2943869 --- /dev/null +++ b/Runtime/Utilities/ReportHardware.h @@ -0,0 +1,19 @@ +#ifndef __REPORT_HARDWARE_H +#define __REPORT_HARDWARE_H + +class WWW; + + +class HardwareInfoReporter { +public: + HardwareInfoReporter() : m_InfoPost(NULL) { }; + + void ReportHardwareInfo(); + void Shutdown(); + +private: + WWW* m_InfoPost; +}; + + +#endif diff --git a/Runtime/Utilities/SpatialHash.cpp b/Runtime/Utilities/SpatialHash.cpp new file mode 100644 index 0000000..4865bd4 --- /dev/null +++ b/Runtime/Utilities/SpatialHash.cpp @@ -0,0 +1,90 @@ +#include "UnityPrefix.h" +#include "SpatialHash.h" +#include "External/MurmurHash/MurmurHash2.h" +#include "Runtime/Geometry/AABB.h" +#include <float.h> + + +// quantisation of direction +inline unsigned QuantiseDirection(const Vector3f& dir) +{ + // quantise direction + unsigned id; + if (fabsf(dir.x) >= fabsf(dir.y) && fabsf(dir.x) >= fabsf(dir.z)) + id = dir.x > 0 ? 0 : 1; + else if (fabsf(dir.y) >= fabsf(dir.z)) + id = dir.y > 0 ? 2 : 3; + else + id = dir.z > 0 ? 4 : 5; + return id; +} + +// quantisation of position +inline void QuantisePosition(const Vector3f& pos, float voxelSize, int* xyz) +{ + // quantise position + const float cell_side = voxelSize; + const float cell_height = voxelSize * kVoxelHeightMultiplier; + xyz[0] = (int)std::floor(pos.x / cell_side); + xyz[1] = (int)std::floor(pos.y / cell_height); + xyz[2] = (int)std::floor(pos.z / cell_side); +} + +inline AABB QuantisedValueToAABB(int* quantisedValue, float voxelSize) +{ + const float cell_side = voxelSize; + const float cell_height = voxelSize * kVoxelHeightMultiplier; + const Vector3f pos(quantisedValue[0]*cell_side,quantisedValue[1]*cell_height,quantisedValue[2]*cell_side); + const Vector3f halfDir( cell_side*.5f, cell_height*.5f, cell_side*.5f ); + return AABB(pos + halfDir, Abs(halfDir)); +} + +// compute hash index for directions cache +inline UInt64 ComputeHash(const Vector3f& pos, const Vector3f& dir, float voxelSize ) +{ + int qp[3]; + QuantisePosition(pos, voxelSize, qp); + UInt64 key = MurmurHash64A( (void*)qp, sizeof(int)*3, 0xdeadbeef186726feULL ); + // quantise direction + unsigned id = QuantiseDirection(dir); + key = key ^ id; + return key; +} + +///////////////////////////////////////////////////// +// Collision plane cache members +PlaneColliderCache_dense_hashmap::PlaneColliderCache_dense_hashmap() +{ + m_PlaneHashMap.set_empty_key (ComputeHash(Vector3f(FLT_MAX,FLT_MAX,FLT_MAX),Vector3f(FLT_MAX,FLT_MAX,FLT_MAX),1.f)); + m_PlaneHashMap.set_deleted_key (ComputeHash(Vector3f(FLT_MIN,FLT_MIN,FLT_MIN),Vector3f(FLT_MIN,FLT_MIN,FLT_MIN),1.f)); +} + +bool PlaneColliderCache_dense_hashmap::Replace (const Vector3f& pos, const Vector3f& dir, const Plane& plane, int colliderInstanceID, int rigidBodyOrColliderInstanceID, float voxelSize) +{ + UInt64 hash = ComputeHash (pos, dir, voxelSize); + int sz = Size(); + m_PlaneHashMap.insert (std::make_pair (hash, PlaneData (plane, colliderInstanceID, rigidBodyOrColliderInstanceID))); + if ( sz >= Size() ) + { + PlaneHashMap::iterator it = m_PlaneHashMap.find (hash); + if (it == m_PlaneHashMap.end ()) + return false; + it->second.m_Plane = plane; + it->second.m_ColliderInstanceID = colliderInstanceID; + it->second.m_RigidBodyOrColliderInstanceID = rigidBodyOrColliderInstanceID; + } + return true; +} + +bool PlaneColliderCache_dense_hashmap::Find (const Vector3f& pos, const Vector3f& dir, Plane& plane, int& colliderInstanceID, int& rigidBodyOrColliderInstanceID, float voxelSize) const +{ + UInt64 hash = ComputeHash (pos, dir, voxelSize); + PlaneHashMap::const_iterator it = m_PlaneHashMap.find (hash); + if (it == m_PlaneHashMap.end ()) + return false; + plane = it->second.m_Plane; + colliderInstanceID = it->second.m_ColliderInstanceID; + rigidBodyOrColliderInstanceID = it->second.m_RigidBodyOrColliderInstanceID; + return true; +} + diff --git a/Runtime/Utilities/SpatialHash.h b/Runtime/Utilities/SpatialHash.h new file mode 100644 index 0000000..74dd01e --- /dev/null +++ b/Runtime/Utilities/SpatialHash.h @@ -0,0 +1,50 @@ +#pragma once + +#include "Runtime/Geometry/Plane.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Utilities/dense_hash_map.h" +#include <algorithm> + +static const float kVoxelHeightMultiplier = 4.f; + +struct PlaneData +{ + PlaneData () {} + PlaneData (const Plane& plane, int colliderInstanceID, int rigidBodyOrColliderInstanceID) + : m_Plane (plane) + , m_ColliderInstanceID (colliderInstanceID) + , m_RigidBodyOrColliderInstanceID (rigidBodyOrColliderInstanceID) + {} + Plane m_Plane; + int m_ColliderInstanceID; + int m_RigidBodyOrColliderInstanceID; +}; + +// Collision plane cache using the google dense hashmap +class PlaneColliderCache_dense_hashmap +{ +public: + PlaneColliderCache_dense_hashmap (); + ~PlaneColliderCache_dense_hashmap () {Clear();} + int Size () const { return m_PlaneHashMap.size(); }; + bool Find (const Vector3f& pos, const Vector3f& dir, Plane& plane, int& colliderInstanceID, int& rigidBodyOrColliderInstanceID, float voxelSize) const; + bool Replace (const Vector3f& pos, const Vector3f& dir, const Plane& plane, int colliderInstanceID, int rigidBodyOrColliderInstanceID, float voxelSize); + void Clear () + { + m_PlaneHashMap.clear (); + } +private: + // Hash map for cached planes + struct UInt64HashFunctor + { + inline size_t operator ()(UInt64 x) const + { + return int(x >> 32); + } + }; + typedef std::pair<const UInt64, PlaneData> KeyToPlanePair; + typedef dense_hash_map<UInt64, PlaneData, UInt64HashFunctor, std::equal_to<UInt64>, STL_ALLOCATOR (kMemSTL, KeyToPlanePair) > PlaneHashMap; + PlaneHashMap m_PlaneHashMap; +}; + +typedef PlaneColliderCache_dense_hashmap PlaneColliderCache; diff --git a/Runtime/Utilities/Stacktrace.cpp b/Runtime/Utilities/Stacktrace.cpp new file mode 100644 index 0000000..e87b53a --- /dev/null +++ b/Runtime/Utilities/Stacktrace.cpp @@ -0,0 +1,246 @@ +#include "UnityPrefix.h" + +#define HAS_POSIX_BACKTRACE UNITY_OSX || UNITY_ANDROID +#if HAS_POSIX_BACKTRACE && UNITY_OSX + #define POSIX_BACKTRACE_DECL extern "C" +#else + #define POSIX_BACKTRACE_DECL +#endif + +#if UNITY_WIN +#include <winnt.h> +#include "PlatformDependent/Win/StackWalker.h" +typedef USHORT (WINAPI *CaptureStackBackTraceType)(__in ULONG, __in ULONG, __out PVOID*, __out_opt PULONG); +CaptureStackBackTraceType backtrace = NULL; +// Then use 'backtrace' as if it were CaptureStackBackTrace + +class MyStackWalker: public StackWalker +{ +protected: + virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) {} + virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion) {} + virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry) + { + if(*entry.undFullName != '\0') + OnOutput(entry.undFullName); + else if(*entry.undName != '\0') + OnOutput(entry.undName); + else if(*entry.name != '\0') + OnOutput(entry.name); + OnOutput("\n"); + } + virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) {} +}; + +class StringStackWalker: public MyStackWalker +{ +public: + StringStackWalker(): result(NULL) { } + + void SetOutputString(std::string &_result) {result = &_result;} +protected: + std::string *result; + + virtual void OnOutput(LPCSTR szTest) + { + if(result) + *result += szTest; + } +}; + +class BufferStackWalker: public MyStackWalker +{ +public: + BufferStackWalker(): maxSize(0) { } + + void SetOutputBuffer(char *_buffer, int _maxSize) + { + buffer = _buffer; + maxSize = _maxSize; + } +protected: + char *buffer; + int maxSize; + + virtual void OnOutput(LPCSTR szTest) + { + while(maxSize > 1 && *szTest != '\0') + { + *(buffer++) = *(szTest++); + maxSize--; + } + *buffer = '\0'; + } +}; +#elif HAS_POSIX_BACKTRACE +POSIX_BACKTRACE_DECL int backtrace(void** buffer, int size); +POSIX_BACKTRACE_DECL char** backtrace_symbols(void* const* buffer, int size); +#include "Runtime/Mono/MonoIncludes.h" + #if UNITY_ANDROID + #include <corkscrew/backtrace.h> + #include "Runtime/Mono/MonoIncludes.h" + #endif +#endif + +#if UNITY_WIN +StringStackWalker* g_StringStackWalker = NULL; +BufferStackWalker* g_BufferStackWalker = NULL; +#endif + +void InitializeStackWalker(){ +#if UNITY_WIN + if (g_BufferStackWalker == NULL){ + g_BufferStackWalker = new BufferStackWalker(); + } + if (g_StringStackWalker == NULL){ + g_StringStackWalker = new StringStackWalker(); + } +#endif +} + +std::string GetStacktrace(bool includeMonoStackFrames) +{ + std::string trace; +#if UNITY_WIN + InitializeStackWalker(); + g_StringStackWalker->SetOutputString(trace); + if (includeMonoStackFrames) + g_StringStackWalker->ShowCallstackSimple(); + else + g_StringStackWalker->ShowCallstack(); +#elif HAS_POSIX_BACKTRACE + void* backtraceFrames[128]; + int frameCount = backtrace(&backtraceFrames[0], 128); + char** frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount); + + if(frameStrings != NULL) + { + for(int x = 0; x < frameCount; x++) + { + if(frameStrings[x] != NULL) + { + std::string str = frameStrings[x]; + if(str.find("???") != std::string::npos) + { + // If the symbol could not be resolved, try if mono knows about the address. + unsigned int addr = 0; + if (sscanf(str.substr(str.find("0x")).c_str(), "0x%x", &addr)) + { + if(addr == -1){ + break; + } + const char *mono_frame = mono_pmip((void*)addr); + // If mono knows the address, replace the stack trace line with + // the information from mono. + if (mono_frame) + str = str.substr(0,4) + "[Mono JITed code] " + mono_frame; + } + } + trace = trace + str + '\n'; + } + } + free(frameStrings); + } +#else + trace = "Stacktrace is not supported on this platform."; +#endif + return trace; +} + +UInt32 GetStacktrace(void** trace, int maxFrames, int startframe) +{ +#if UNITY_WIN + if(!backtrace) + backtrace = (CaptureStackBackTraceType)(GetProcAddress(LoadLibrary("kernel32.dll"), "RtlCaptureStackBackTrace")); + DWORD dwHashCode = 0; + int count = backtrace(startframe, maxFrames-1, trace, &dwHashCode); + trace[count] = 0; + return dwHashCode; +// InitializeStackWalker(); +// g_BufferStackWalker->GetCurrentCallstack(trace,maxFrames); +#elif HAS_POSIX_BACKTRACE + int framecount = backtrace(trace, maxFrames); + trace[framecount] = 0; + int index = startframe; + UInt32 hash = 0; + while(trace[index]) + { + trace[index-startframe] = trace[index]; + hash ^= (UInt32)(trace[index]) ^ (hash << 7) ^ (hash >> 21); + index++; + } + trace[index-startframe] = 0; + return hash; +#endif + return 0; +} + +void GetReadableStackTrace(char* output, int bufsize, void** input, int frameCount) +{ +#if UNITY_WIN + InitializeStackWalker(); + g_BufferStackWalker->GetStringFromStacktrace(output, bufsize, input); +#elif HAS_POSIX_BACKTRACE + char **frameStrings = backtrace_symbols(input, frameCount); + std::string trace; + if(frameStrings != NULL) + { + for(int x = 0; x < frameCount; x++) + { + if(frameStrings[x] != NULL) + { + #if UNITY_ANDROID + char line[256] = {0}; + snprintf(line, 255, " #%02d %s\n", x, frameStrings[x]); + trace = trace + line; + #else + trace = trace + frameStrings[x] + '\n'; + #endif + } + } + free(frameStrings); + } + strcpy(output,trace.c_str()); +#endif + +} + +void GetStacktrace(char *trace, int maxSize, int maxFrames) +{ +#if UNITY_WIN + InitializeStackWalker(); + g_BufferStackWalker->SetOutputBuffer(trace, maxSize); + g_BufferStackWalker->ShowCallstack( + GetCurrentThread(), + NULL, + NULL, + NULL, + maxFrames); +#elif HAS_POSIX_BACKTRACE + void *backtraceFrames[128]; + int frameCount = backtrace(&backtraceFrames[0], 128); + char **frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount); + frameCount = std::min(frameCount, maxFrames); + if(frameStrings != NULL) + { + for(int x = 0; x < frameCount; x++) + { + if(frameStrings[x] != NULL) + { + #if UNITY_ANDROID + int numChars = snprintf(trace, maxSize, " #%02d %s\n", x, frameStrings[x]); + #else + int numChars = snprintf(trace, maxSize, "%s\n", frameStrings[x]); + #endif + trace += numChars; + maxSize -= numChars; + } + if(maxSize <= 0) + break; + } + free(frameStrings); + } +#else + strncpy(trace, "Stacktrace is not supported on this platform.", maxSize); +#endif +} diff --git a/Runtime/Utilities/Stacktrace.h b/Runtime/Utilities/Stacktrace.h new file mode 100644 index 0000000..7344e91 --- /dev/null +++ b/Runtime/Utilities/Stacktrace.h @@ -0,0 +1,12 @@ +#ifndef STACKTRACE_H +#define STACKTRACE_H + +void InitializeStackWalker(); + +std::string GetStacktrace(bool includeMonoStackFrames = false); + +//can't use string version when debugging new/delete allocations, as string class may allocate memory itself! +void GetStacktrace(char *trace, int maxSize, int maxFrames = INT_MAX); +UInt32 GetStacktrace(void** trace, int maxFrames, int startframe); +void GetReadableStackTrace(char* output, int bufsize, void** input, int count); +#endif diff --git a/Runtime/Utilities/StaticAssert.h b/Runtime/Utilities/StaticAssert.h new file mode 100644 index 0000000..874b526 --- /dev/null +++ b/Runtime/Utilities/StaticAssert.h @@ -0,0 +1,55 @@ +#pragma once +#include "Configuration/UnityConfigure.h" +#include "Runtime/Utilities/TypeUtilities.h" + +#ifndef SUPPORT_CPP0X_STATIC_ASSERT + #if (defined _MSC_VER) && (_MSC_VER >= 1600) + #define SUPPORT_CPP0X_STATIC_ASSERT 1 + #elif (defined __has_extension) + #define SUPPORT_CPP0X_STATIC_ASSERT __has_extension(cxx_static_assert) + #else + #define SUPPORT_CPP0X_STATIC_ASSERT 0 + #endif +#endif + +#if SUPPORT_CPP0X_STATIC_ASSERT + #define CompileTimeAssert(expression, message) static_assert(expression, message) +#else + template<bool> struct CompileTimeAssertImpl; + template<> struct CompileTimeAssertImpl<true> {}; + + #define CT_ASSERT_HACK_JOIN(X,Y) CT_ASSERT_HACK_DO_JOIN(X,Y) + #define CT_ASSERT_HACK_DO_JOIN(X,Y) CT_ASSERT_HACK_DO_JOIN2(X,Y) + #define CT_ASSERT_HACK_DO_JOIN2(X,Y) X##Y + + #define CompileTimeAssert(expression, message) \ + enum{ CT_ASSERT_HACK_JOIN(ct_assert_, __LINE__) = sizeof(CompileTimeAssertImpl<(expression)>) } +#endif + + +#ifndef SUPPORT_CPP0X_DECLTYPE + #if (defined _MSC_VER) && (_MSC_VER >= 1600) + #define SUPPORT_CPP0X_DECLTYPE 1 + #elif (defined __has_extension) + #define SUPPORT_CPP0X_DECLTYPE __has_extension(cxx_decltype) + #else + #define SUPPORT_CPP0X_DECLTYPE 0 + #endif +#endif + + +#if SUPPORT_CPP0X_STATIC_ASSERT && SUPPORT_CPP0X_DECLTYPE + /// Usage Example: + /// int a; + /// double b; + /// AssertVariableType(a, int); // OK + /// AssertVariableType(b, int); // Error during compile time + /// AssertIfVariableType(a, int); // Error during compile time + /// AssertIfVariableType(b, int); // OK + + #define AssertVariableType(Variable, Type) CompileTimeAssert(IsSameType<Type,decltype(Variable)>::result, #Variable" is not of type "#Type) + #define AssertIfVariableType(Variable, Type) CompileTimeAssert(!IsSameType<Type,decltype(Variable)>::result, #Variable" is of type "#Type) +#else + #define AssertVariableType(Variable, Type) do { (void)sizeof(Variable); } while(0) + #define AssertIfVariableType(Variable, Type) do { (void)sizeof(Variable); } while(0) +#endif diff --git a/Runtime/Utilities/StrideIterator.h b/Runtime/Utilities/StrideIterator.h new file mode 100644 index 0000000..c36877e --- /dev/null +++ b/Runtime/Utilities/StrideIterator.h @@ -0,0 +1,89 @@ +#ifndef STRIDE_ITERATOR_H_ +#define STRIDE_ITERATOR_H_ + +#include <iterator> + +template<class T> +class StrideIterator : public std::iterator<std::random_access_iterator_tag, T> +{ +public: + //@TODO: Get rid of all stl usage and remove this crap. + typedef std::iterator<std::random_access_iterator_tag, T> base_iterator; + typedef typename base_iterator::value_type value_type; + typedef typename base_iterator::difference_type difference_type; + typedef typename base_iterator::pointer pointer; + typedef typename base_iterator::reference reference; + typedef typename base_iterator::iterator_category iterator_category; + + StrideIterator () : m_Pointer(NULL), m_Stride(1) {} + StrideIterator (void* p, int stride) : m_Pointer (reinterpret_cast<UInt8*> (p)), m_Stride (stride) {} + StrideIterator (StrideIterator const& arg) : m_Pointer(arg.m_Pointer), m_Stride(arg.m_Stride) {} + + void operator = (StrideIterator const& arg) { m_Pointer = arg.m_Pointer; m_Stride = arg.m_Stride; } + + bool operator == (StrideIterator const& arg) const { return m_Pointer == arg.m_Pointer; } + bool operator != (StrideIterator const& arg) const { return m_Pointer != arg.m_Pointer; } + + bool operator < (StrideIterator const& arg) const { return m_Pointer < arg.m_Pointer; } + + void operator++() { m_Pointer += m_Stride; } + void operator++(int) { m_Pointer += m_Stride; } + + StrideIterator operator + (difference_type n) const { return StrideIterator (m_Pointer + m_Stride * n, m_Stride); } + void operator += (difference_type n) { m_Pointer += m_Stride * n; } + + difference_type operator-(StrideIterator const& it) const + { + Assert (m_Stride == it.m_Stride && "Iterators stride must be equal"); + Assert (m_Stride != 0 && "Stide must not be zero"); + return ((uintptr_t)m_Pointer - (uintptr_t)it.m_Pointer) / m_Stride; + } + + T& operator[](size_t index) { return *reinterpret_cast<T*> (m_Pointer + m_Stride * index); } + const T& operator[](size_t index) const { return *reinterpret_cast<const T*> (m_Pointer + m_Stride * index); } + + T& operator*() { return *reinterpret_cast<T*> (m_Pointer); } + const T& operator*() const { return *reinterpret_cast<const T*> (m_Pointer); } + + T* operator->() { return reinterpret_cast<T*> (m_Pointer); } + const T* operator->() const { return reinterpret_cast<const T*> (m_Pointer); } + + // Iterator is NULL if not valid + bool IsNull () const { return m_Pointer == 0; } + void* GetPointer() const { return m_Pointer; } + int GetStride() const { return m_Stride; } + +private: + UInt8* m_Pointer; + int m_Stride; +}; + +template<class T> +void strided_copy (const T* src, const T* srcEnd, StrideIterator<T> dst) +{ + for (; src != srcEnd ; src++, ++dst) + *dst = *src; +} + +template<class T> +void strided_copy (StrideIterator<T> src, StrideIterator<T> srcEnd, StrideIterator<T> dst) +{ + for (; src != srcEnd ; ++src, ++dst) + *dst = *src; +} + +template<class T> +void strided_copy (StrideIterator<T> src, StrideIterator<T> srcEnd, T* dst) +{ + for (; src != srcEnd ; ++src, ++dst) + *dst = *src; +} + +template<class T, class T2> +void strided_copy_convert (const T* src, const T* srcEnd, StrideIterator<T2> dst) +{ + for (; src != srcEnd ; ++src, ++dst) + *dst = *src; +} + +#endif // STRIDE_ITERATOR_H_ diff --git a/Runtime/Utilities/TypeUtilities.h b/Runtime/Utilities/TypeUtilities.h new file mode 100644 index 0000000..3455701 --- /dev/null +++ b/Runtime/Utilities/TypeUtilities.h @@ -0,0 +1,20 @@ +// Utility functions and types for working with static types. +#pragma once + +template<typename T1, typename T2> +struct IsSameType +{ + static const bool result = false; +}; + +template<typename T> +struct IsSameType<T, T> +{ + static const bool result = true; +}; + +template<typename Expected, typename Actual> +inline bool IsOfType (Actual& value) +{ + return IsSameType<Actual, Expected>::result; +} diff --git a/Runtime/Utilities/URLUtility.h b/Runtime/Utilities/URLUtility.h new file mode 100644 index 0000000..39cbadd --- /dev/null +++ b/Runtime/Utilities/URLUtility.h @@ -0,0 +1,8 @@ +#ifndef URLUTILITY_H +#define URLUTILITY_H + +#include <string> + +void OpenURL (const std::string& url); + +#endif diff --git a/Runtime/Utilities/UniqueIDGenerator.cpp b/Runtime/Utilities/UniqueIDGenerator.cpp new file mode 100644 index 0000000..5649997 --- /dev/null +++ b/Runtime/Utilities/UniqueIDGenerator.cpp @@ -0,0 +1,34 @@ +#include "UnityPrefix.h" +#include "UniqueIDGenerator.h" + +UniqueIDGenerator::UniqueIDGenerator() +{ + m_IDs.clear(); + m_IDs.push_back(2); // Generated ID sequence should not contain 0 + m_free = 1; +} + +unsigned int UniqueIDGenerator::AllocateID () +{ + DebugAssert( m_free <= m_IDs.size() ); + + if (m_free == m_IDs.size()) + { + m_IDs.push_back(m_free+1); + } + + unsigned int result = m_free; + m_free = m_IDs[m_free]; + + DebugAssert(result != 0); + + return result; +} + +void UniqueIDGenerator::RemoveID (unsigned int _ID) +{ + DebugAssert( (_ID > 0) && (_ID < m_IDs.size()) ); + + m_IDs[_ID] = m_free; + m_free = _ID; +} diff --git a/Runtime/Utilities/UniqueIDGenerator.h b/Runtime/Utilities/UniqueIDGenerator.h new file mode 100644 index 0000000..b5f7386 --- /dev/null +++ b/Runtime/Utilities/UniqueIDGenerator.h @@ -0,0 +1,18 @@ +#pragma once + +#include "UnityPrefix.h" +#include "Runtime/Utilities/dynamic_array.h" + + +// Class to generate small unique IDs with allocate and remove functions. +class UniqueIDGenerator +{ +public: + UniqueIDGenerator(); + unsigned int AllocateID(); + void RemoveID( unsigned int _ID); + +private: + dynamic_array<unsigned int> m_IDs; + unsigned int m_free; +}; diff --git a/Runtime/Utilities/UnityString.h b/Runtime/Utilities/UnityString.h new file mode 100644 index 0000000..574f42d --- /dev/null +++ b/Runtime/Utilities/UnityString.h @@ -0,0 +1,10 @@ +#ifndef UNITY_STRING_H +#define UNITY_STRING_H + +#if !UNITY_EXTERNAL_TOOL +#if UNITY_WIN +#include "Runtime/Allocator/MemoryMacros.h" +#endif +#endif + +#endif diff --git a/Runtime/Utilities/UserAuthorizationManager.cpp b/Runtime/Utilities/UserAuthorizationManager.cpp new file mode 100644 index 0000000..5513cb6 --- /dev/null +++ b/Runtime/Utilities/UserAuthorizationManager.cpp @@ -0,0 +1,118 @@ +#include "UnityPrefix.h" +#include "UserAuthorizationManager.h" +#include "Runtime/Misc/GameObjectUtility.h" + +#if WEBPLUG +#include "Runtime/Misc/PlayerSettings.h" +#include "Runtime/Utilities/GlobalPreferences.h" +#include "PlatformDependent/CommonWebPlugin/Verification.h" +#endif + +#if UNITY_EDITOR +#include "Editor/Src/EditorUserBuildSettings.h" +#include "Editor/Src/BuildPipeline/BuildTargetPlatformSpecific.h" +#endif + +UserAuthorizationManager gUserAuthorizationManager; + +UserAuthorizationManager& GetUserAuthorizationManager() +{ + return gUserAuthorizationManager; +} + +UserAuthorizationManager::UserAuthorizationManager() +{ + m_AuthorizationOperation = NULL; + Reset (); +} + +#if WEBPLUG && !UNITY_PEPPER +std::string GetUserAuthorizationModeKey () +{ + static const char *kUserAuthorizationModeKey = "UserAuthorizationMode"; + std::string domain = GetStrippedPlayerDomain (); + std::string hash = GenerateHash ((unsigned char*)&domain[0], domain.size()); + return kUserAuthorizationModeKey + hash; +} +#endif + +void UserAuthorizationManager::Reset() +{ +#if WEBPLUG && !UNITY_PEPPER + m_AuthorizationMode = kNone; + if (GetPlayerSettingsPtr()) + { + std::string auth = GetGlobalPreference(GetUserAuthorizationModeKey().c_str()); + sscanf(auth.c_str(), "%x", &m_AuthorizationMode); + } +#else +#if UNITY_EDITOR + if (GetEditorUserBuildSettingsPtr() != NULL && GetBuildTargetGroup(GetEditorUserBuildSettings().GetActiveBuildTarget()) == kPlatformWebPlayer) + m_AuthorizationMode = kNone; + else +#endif + // Allow Webcam and Microphone on all others targets than webplayer. + m_AuthorizationMode = kWebCam | kMicrophone; +#endif + if (m_AuthorizationOperation) + { + m_AuthorizationOperation->Release(); + m_AuthorizationOperation = NULL; + } + m_AuthorizationRequest = kNone; +} + +AsyncOperation *UserAuthorizationManager::RequestUserAuthorization(int mode) +{ + if (m_AuthorizationOperation != NULL) + { + ErrorString ("A RequestUserAuthorization is already pending."); + } + else if (!HasUserAuthorization (mode)) + { + m_AuthorizationRequest = mode; + m_AuthorizationOperation = new UserAuthorizationManagerOperation (); + m_AuthorizationOperation->Retain(); + return m_AuthorizationOperation; + } + + return new UserAuthorizationManagerErrorOperation (); +} + +void UserAuthorizationManager::ReplyToUserAuthorizationRequest(bool reply, bool remember) +{ + if (reply) + { + m_AuthorizationMode |= m_AuthorizationRequest; +#if WEBPLUG && !UNITY_PEPPER + if (remember) + SetGlobalPreference(GetUserAuthorizationModeKey().c_str(), Format("%x", m_AuthorizationMode).c_str()); +#endif + } + m_AuthorizationRequest = kNone; + if (m_AuthorizationOperation) + { + m_AuthorizationOperation->InvokeCoroutine(); + m_AuthorizationOperation->Release(); + m_AuthorizationOperation = NULL; + } +} + +MonoBehaviour *UserAuthorizationManager::GetAuthorizationDialog () +{ + if (m_AuthorizationRequest != kNone) + { + if (!m_AuthorizationDialog.IsValid()) + { + m_AuthorizationDialog = &CreateGameObject ("", "Transform", "UserAuthorizationDialog", NULL); + m_AuthorizationDialog->SetHideFlags (Object::kHideInHierarchy); + } + return m_AuthorizationDialog->QueryComponent (MonoBehaviour); + } + else + { + if (m_AuthorizationDialog.IsValid ()) + DestroyObjectHighLevel (m_AuthorizationDialog, true); + return NULL; + } +} diff --git a/Runtime/Utilities/UserAuthorizationManager.h b/Runtime/Utilities/UserAuthorizationManager.h new file mode 100644 index 0000000..386a22c --- /dev/null +++ b/Runtime/Utilities/UserAuthorizationManager.h @@ -0,0 +1,47 @@ +#ifndef USERAUTHORIZATIONMANAGER_H +#define USERAUTHORIZATIONMANAGER_H + +#include "Runtime/Mono/MonoBehaviour.h" +#include "Runtime/Misc/AsyncOperation.h" + +class UserAuthorizationManager; + +UserAuthorizationManager &GetUserAuthorizationManager(); + +class UserAuthorizationManager { +public: + UserAuthorizationManager (); + + enum Mode { + kNone = 0, + kWebCam = 1 << 0, + kMicrophone = 1 << 1 + }; + + void Reset (); + AsyncOperation *RequestUserAuthorization (int mode); + void ReplyToUserAuthorizationRequest (bool reply, bool remember = false); + bool HasUserAuthorization (int mode) const { return (m_AuthorizationMode & mode) == mode; } + + int GetAuthorizationRequest() const { return m_AuthorizationRequest; } + + MonoBehaviour *GetAuthorizationDialog (); +private: + class UserAuthorizationManagerOperation : public AsyncOperation + { + virtual float GetProgress () { return 0.0f; } + virtual bool IsDone () { return GetUserAuthorizationManager().m_AuthorizationRequest == 0; } + }; + + class UserAuthorizationManagerErrorOperation : public AsyncOperation + { + virtual float GetProgress () { return 0.0f; } + virtual bool IsDone () { return true; } + }; + + int m_AuthorizationMode; + int m_AuthorizationRequest; + UserAuthorizationManagerOperation *m_AuthorizationOperation; + PPtr<GameObject> m_AuthorizationDialog; +}; +#endif
\ No newline at end of file diff --git a/Runtime/Utilities/Utility.h b/Runtime/Utilities/Utility.h new file mode 100644 index 0000000..b24fcba --- /dev/null +++ b/Runtime/Utilities/Utility.h @@ -0,0 +1,214 @@ +#ifndef UTILITY_H +#define UTILITY_H + +#include "Runtime/Allocator/MemoryMacros.h" + +#define UNUSED(x) (void)sizeof(x) + +#define SAFE_RELEASE(obj) if (obj) { (obj)->Release(); (obj) = NULL; } else {} +#define SAFE_RELEASE_LABEL(obj,label) if (obj) { (obj)->Release(label); (obj) = NULL; } else {} + +template <class T0, class T1> +inline void CopyData (T0 *dst, const T1 *src, long int inHowmany) +{ + for (long int i=0;i<inHowmany;i++) + { + dst[i] = src[i]; + } +} + +template <class DataType> +inline void CopyOverlappingData (DataType *dst, const DataType *src, long int inHowmany) +{ + if (dst < src) + { + for (long int i=0;i<inHowmany;i++) + { + dst[i] = src[i]; + } + } + else if (dst > src) + { + for (long int i=inHowmany-1;i>=0;i--) + { + dst[i] = src[i]; + } + } +} + +template <class DataType> +inline void CopyRange (DataType *dst, const DataType *src, long int inStart, long int inHowmany) +{ + for (long int i=inStart;i<inHowmany+inStart;i++) + { + dst[i] = src[i]; + } +} + +template <class DataType> +inline void CopyData (DataType *dst, const DataType src, long int inHowmany) +{ + for (long int i=0;i<inHowmany;i++) + { + dst[i] = src; + } +} + +template <class DataType> +inline void CopyDataReverse (DataType *dst, const DataType *src, long int inHowmany) +{ + for (long int i=0;i<inHowmany;i++) + { + dst[i] = src[inHowmany-1-i]; + } +} + +template <class DataType> +inline bool CompareArrays (const DataType *lhs, const DataType *rhs, long int arraySize) +{ + for (long int i=0; i<arraySize; i++) + { + if (lhs[i] != rhs[i]) + return false; + } + return true; +} + +template <class DataType> +inline bool CompareMemory (const DataType& lhs, const DataType& rhs) +{ +#ifdef ALIGN_OF + // We check at compile time if it's safe to cast data to int* + if (ALIGN_OF(DataType) >= ALIGN_OF(int) && (sizeof(DataType) % sizeof(int))==0) + { + return CompareArrays((const int*)&lhs, (const int*)&rhs, sizeof(DataType) / sizeof(int)); + } +#endif + return CompareArrays((const char*)&lhs, (const char*)&rhs, sizeof(DataType)); +} + +template <class T> +class UNITY_AutoDelete +{ +public: + UNITY_AutoDelete() : m_val(T()) { } + ~UNITY_AutoDelete() { if(m_val) UNITY_DELETE ( m_val, m_label ); } + + void Assign(T val, MemLabelId label) { m_val = val; m_label = label; return *this; } + bool operator!() { return !m_val; } + + /* Releases the internal pointer without deleting */ + T releasePtr() { T tmp = m_val; m_val = T(); return tmp; } +private: + UNITY_AutoDelete &operator=(T val); + UNITY_AutoDelete(const UNITY_AutoDelete<T>& other); // disabled + void operator=(const UNITY_AutoDelete<T>& other); // disabled + T m_val; + MemLabelId m_label; +}; + +class AutoFree +{ +public: + AutoFree() : m_val(NULL), m_label(kMemDefault) { } + ~AutoFree() { if(m_val) UNITY_FREE ( m_label, m_val ); } + + bool operator!() { return !m_val; } + void Assign(void* val, MemLabelId label) { m_label = label; m_val = val; } + + /* Releases the internal pointer without deleting */ + void* releasePtr() { void* tmp = m_val; m_val = NULL; return tmp; } +private: + AutoFree &operator=(void* val); // disabled + AutoFree(const AutoFree& other); // disabled + void operator=(const AutoFree& other); // disabled + void* m_val; + MemLabelId m_label; +}; + +template <class T> +inline T clamp (const T&t, const T& t0, const T& t1) +{ + if (t < t0) + return t0; + else if (t > t1) + return t1; + else + return t; +} + +template <> +inline float clamp (const float&t, const float& t0, const float& t1) +{ +#if UNITY_XENON || UNITY_PS3 + return FloatMin(FloatMax(t, t0), t1); +#else + if (t < t0) + return t0; + else if (t > t1) + return t1; + else + return t; +#endif +} + +template <class T> +inline T clamp01 (const T& t) +{ + if (t < 0) + return 0; + else if (t > 1) + return 1; + else + return t; +} + +template <> +inline float clamp01<float> (const float& t) +{ +#if UNITY_XENON || UNITY_PS3 + return FloatMin(FloatMax(t, 0.0f), 1.0f); +#else + if (t < 0.0F) + return 0.0F; + else if (t > 1.0F) + return 1.0F; + else + return t; +#endif +} + +// Asserts if from is NULL or can't be cast to type To +template<class To, class From> inline +To assert_cast (From from) +{ + AssertIf (dynamic_cast<To> (from) == NULL); + return static_cast<To> (from); +} + +inline float SmoothStep (float from, float to, float t) +{ + t = clamp01(t); + t = -2.0F * t*t*t + 3.0F * t*t; + return to * t + from * (1.0f - t); +} +// Rounds value down. +// Note: base must be power of two value. +inline UInt32 RoundDown (UInt32 value, SInt32 base) +{ + return value & (-base); +} +// Rounds value up. +// Note: base must be power of two value. +inline UInt32 RoundUp (UInt32 value, SInt32 base) +{ + return (value + base - 1) & (-base); +} + +template<class T> +inline T* Stride (T* p, size_t offset) +{ + return reinterpret_cast<T*>((char*)p + offset); +} + +#endif // include-guard diff --git a/Runtime/Utilities/UtilityTests.cpp b/Runtime/Utilities/UtilityTests.cpp new file mode 100644 index 0000000..daef426 --- /dev/null +++ b/Runtime/Utilities/UtilityTests.cpp @@ -0,0 +1,629 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" + +#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" + +#include "Runtime/Utilities/LinkedList.h" +#include "Runtime/Utilities/MemoryPool.h" +#include "Runtime/Utilities/File.h" +#include <string.h> +#include "Runtime/Utilities/dynamic_bitset.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Utilities/vector_set.h" +#include "Runtime/Utilities/ArrayUtility.h" +#include "Runtime/Allocator/FixedSizeAllocator.h" + + +#if !GAMERELEASE +#include "Runtime/Utilities/PathNameUtility.h" +#include "Runtime/Utilities/FileUtilities.h" +#include "Runtime/Utilities/GUID.h" +UnityGUID StringToGUID (const std::string& pathName); +static const char* kTempFolder = "Temp"; +#endif + + +SUITE (UtilityTests) +{ + + +TEST (TestMemoryPool) +{ + const int kBubble = 256; + const int kBubbleSize = 64; + + // memory pool with 128 elements per bubble + MemoryPool pool (true, "TestPool", kBubbleSize, kBubble * kBubbleSize); + + std::vector<void*> ptrs; + + // allocate 128, should be one bubble and 128 objects + for (int i = 0; i < kBubble; ++i) + { + ptrs.push_back (pool.Allocate ()); + } + CHECK_EQUAL (kBubble, pool.GetAllocCount ()); + CHECK_EQUAL (1, pool.GetBubbleCount ()); + + // Allocate 1 more, should be 2 bubbles and kBubble+1 objects + ptrs.push_back (pool.Allocate ()); + CHECK_EQUAL (kBubble + 1, pool.GetAllocCount ()); + CHECK_EQUAL (2, pool.GetBubbleCount ()); + + // deallocate first bubble + void* last = ptrs[kBubble]; + for (int i = 0; i < kBubble; ++i) + { + pool.Deallocate (ptrs[i]); + } + ptrs.clear (); + ptrs.push_back (last); + CHECK_EQUAL (1, pool.GetAllocCount()); + CHECK_EQUAL (2, pool.GetBubbleCount()); + + // now we have two bubbles: 1st totally free, 2nd one with 1 allocation + // allocate 255 more objects. Should all fit into existing bubbles! + for (int i = 0; i < kBubble * 2 - 1; ++i) + { + ptrs.push_back (pool.Allocate ()); + } + CHECK_EQUAL (kBubble * 2, pool.GetAllocCount ()); + CHECK_EQUAL (2, pool.GetBubbleCount ()); + + // Allocate one more. Should cause additional bubble + ptrs.push_back (pool.Allocate ()); + CHECK_EQUAL (kBubble * 2 + 1, pool.GetAllocCount ()); + CHECK_EQUAL (3, pool.GetBubbleCount ()); + + // Deallocate all. + for (int i = 0; i < ptrs.size (); ++i) + { + pool.Deallocate (ptrs[i]); + } + ptrs.clear (); + CHECK_EQUAL (0, pool.GetAllocCount ()); + CHECK_EQUAL (3, pool.GetBubbleCount ()); + + // now we have three totally free bubbles + // allocate 3*128 objects. Should fit into existing bubbles! + for (int i = 0; i < kBubble * 3; ++i) + { + ptrs.push_back (pool.Allocate ()); + } + CHECK_EQUAL (kBubble * 3, pool.GetAllocCount ()); + CHECK_EQUAL (3, pool.GetBubbleCount ()); + + // deallocate all from last bubble + for (int i = kBubble * 2; i < kBubble * 3; ++i) + { + pool.Deallocate (ptrs[i]); + } + ptrs.resize (kBubble * 2); + CHECK_EQUAL (kBubble * 2, pool.GetAllocCount ()); + CHECK_EQUAL (3, pool.GetBubbleCount ()); + + // allocate one more + ptrs.push_back (pool.Allocate ()); + CHECK_EQUAL (kBubble * 2 + 1, pool.GetAllocCount ()); + CHECK_EQUAL (3, pool.GetBubbleCount ()); + + pool.DeallocateAll (); + ptrs.clear (); + CHECK_EQUAL (0, pool.GetAllocCount ()); + CHECK_EQUAL (0, pool.GetBubbleCount ()); + + // Test pre-allocation: preallocate 1.5 worth of bubbles + pool.PreallocateMemory (kBubble * kBubbleSize * 3/2); + // should result in 2 bubbles + CHECK_EQUAL (2, pool.GetBubbleCount ()); + for (int i = 0; i < kBubble * 2; ++i) + { + ptrs.push_back (pool.Allocate ()); + } + CHECK_EQUAL (kBubble * 2, pool.GetAllocCount ()); + CHECK_EQUAL (2, pool.GetBubbleCount ()); + + // Allocate one more, should create additional bubble + ptrs.push_back (pool.Allocate ()); + CHECK_EQUAL (kBubble * 2 + 1, pool.GetAllocCount ()); + CHECK_EQUAL (3, pool.GetBubbleCount ()); + pool.DeallocateAll (); +} + + +#if !GAMERELEASE +TEST (TestPathExist) +{ + using namespace std; + + CreateDirectory (kTempFolder); + + string filePath = AppendPathName (kTempFolder, "TestIsFileCreated"); + DeleteFileOrDirectory (filePath); + + CHECK (!IsPathCreated (filePath)); + CHECK (!IsDirectoryCreated (filePath)); + CHECK (!IsFileCreated (filePath)); + + CreateFile (filePath); + + CHECK (IsPathCreated (kTempFolder)); + CHECK (IsDirectoryCreated (kTempFolder)); + CHECK (!IsFileCreated (kTempFolder)); + + CHECK (IsPathCreated (filePath)); + CHECK (!IsDirectoryCreated (filePath)); + CHECK (IsFileCreated (filePath)); +} +#endif + + +TEST (DynamicArray) +{ + // no allocation for empty array + dynamic_array<int> array; + CHECK_EQUAL (0, array.capacity ()); + + // push_back allocates + int j = 1; + array.push_back (j); + CHECK_EQUAL (1, array.size ()); + CHECK (array.capacity () > 0); + + // push_back(void) + int& i = array.push_back (); + i = 666; + CHECK_EQUAL(666, array.back ()); + + // clear frees memory? + array.clear (); + CHECK_EQUAL (0, array.size ()); + CHECK_EQUAL (0, array.capacity ()); + + // 3 item list + j = 6; + array.push_back (j); + j = 7; + array.push_back (j); + j = 8; + array.push_back (j); + + CHECK_EQUAL (3, array.size ()); + + // swapping + dynamic_array<int> ().swap (array); + CHECK_EQUAL (0, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // reserve + array.reserve (1024); + CHECK_EQUAL (1024, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // copy assignment + dynamic_array<int> array1; + j = 888; + array1.push_back (j); + + array = array1; + + CHECK_EQUAL (1, array.size ()); + CHECK_EQUAL (888, array.back ()); + CHECK_EQUAL (1, array1.size ()); + CHECK_EQUAL (888, array1.back ()); +} + + struct Stuff + { + int value; + int identifier; + + bool operator < (const Stuff& rhs) const + { + return value < rhs.value; + } + + Stuff (int a, int b) { value = a; identifier = b; } + }; + + +TEST (Test_vector_set_assign_clear_duplicates) +{ + // Make sure that duplicates are removed, + // but also that only the first same instance is maintained, the following ones are killed + Stuff input[] = { Stuff (10, 0), Stuff (11, 1), Stuff (3, 2), Stuff (3, 3), Stuff (4, 4), Stuff (10, 5) }; + Stuff output[] = { Stuff (3, 2), Stuff (4, 4), Stuff (10, 0), Stuff (11, 1) }; + + vector_set<Stuff> test_set; + test_set.assign_clear_duplicates(input, input + ARRAY_SIZE(input)); + + CHECK_EQUAL(test_set.size(), ARRAY_SIZE(output)); + for (int i=0;i<ARRAY_SIZE(output);i++) + { + CHECK_EQUAL(output[i].value, test_set[i].value); + CHECK_EQUAL(output[i].identifier, test_set[i].identifier); + } +} + +class TestNode : public ListElement +{ +}; + +TEST (TestList) +{ + typedef List<TestNode> ListType; + + struct + { + void operator () (ListType& list, TestNode* nodes[], int count) + { + CHECK_EQUAL (count, list.size_slow ()); + int c = 0; + for (ListType::iterator i = list.begin (); i != list.end (); ++i) + { + CHECK (nodes[c] == &*i); + ++c; + } + + CHECK_EQUAL (count, c); + } + } CheckNodes; + + ListType list, emptyList, emptyList2; + + CHECK_EQUAL (0, emptyList.size_slow ()); + emptyList.clear (); + CHECK_EQUAL (0, emptyList.size_slow ()); + + TestNode* nodes[] = + { + new TestNode (), + new TestNode (), + new TestNode (), + new TestNode (), + new TestNode (), + new TestNode () + }; + + emptyList.swap (emptyList2); + CHECK_EQUAL (0, emptyList.size_slow ()); + CHECK_EQUAL (0, emptyList2.size_slow ()); + + // insertion and pushback + list.push_back (*nodes[1]); + list.insert (nodes[1], *nodes[0]); + list.push_back (*nodes[2]); + list.push_back (*nodes[3]); + list.push_back (*nodes[5]); + list.insert (nodes[5], *nodes[4]); + CheckNodes (list, nodes, 6); + // insert before self + list.insert (list.begin(), *list.begin ()); + CheckNodes (list, nodes, 6); + + list.append (emptyList); + CHECK_EQUAL (0, emptyList.size_slow ()); + CheckNodes (list, nodes, 6); + + // append remove into something empty + emptyList.append (list); + CHECK_EQUAL (0, list.size_slow ()); + CheckNodes (emptyList, nodes, 6); + + // invert operation by doing a swap + emptyList.swap (list); + CHECK_EQUAL (0, emptyList.size_slow ()); + CheckNodes (list, nodes, 6); + + // Create another list to test copying + TestNode* nodes2[] = + { + new TestNode (), + new TestNode (), + new TestNode () + }; + ListType list2; + list2.push_back (*nodes2[1]); + list2.push_front (*nodes2[0]); + list2.push_back (*nodes2[2]); + CheckNodes (list2, nodes2, 3); + + // swap back and forth + list2.swap (list); + CheckNodes (list, nodes2, 3); + CheckNodes (list2, nodes, 6); + list.swap (list2); + CheckNodes (list, nodes, 6); + CheckNodes (list2, nodes2, 3); + + list.append (list2); // insert before self + int c = 0; + for (ListType::iterator i = list.begin (); i != list.end (); ++i) + { + if (c >= 6) + CHECK (nodes2[c - 6] == &*i); + else + CHECK (nodes[c] == &*i); + ++c; + } + CHECK_EQUAL (9, list.size_slow ()); + CHECK_EQUAL (0, list2.size_slow ()); + CHECK_EQUAL (9, c); + + emptyList.append (emptyList2); + CHECK_EQUAL (0, emptyList2.size_slow ()); + CHECK_EQUAL (0, emptyList.size_slow ()); +} + +TEST (DynamicBitSet) +{ + dynamic_bitset set; + UInt32 block = 1 << 0 | 1 << 3 | 1 << 5; + set.resize (6); + from_block_range (&block, &block + 1, set); + + CHECK_EQUAL (true, set.test (0)); + CHECK_EQUAL (false, set.test (1)); + CHECK_EQUAL (false, set.test (2)); + CHECK_EQUAL (true, set.test (3)); + CHECK_EQUAL (false, set.test (4)); + CHECK_EQUAL (true, set.test (5)); + + to_block_range (set, &block); + bool res; + + res = block & (1 << 0); + CHECK_EQUAL (true, res); + res = block & (1 << 1); + CHECK_EQUAL (false, res); + res = block & (1 << 2); + CHECK_EQUAL (false, res); + res = block & (1 << 3); + CHECK_EQUAL (true, res); + res = block & (1 << 4); + CHECK_EQUAL (false, res); + res = block & (1 << 5); + CHECK_EQUAL (true, res); +} + + +TEST (DynamicArrayMisc) +{ + // no allocation for empty array + dynamic_array<int> array; + CHECK_EQUAL (0, array.capacity ()); + CHECK (array.owns_data ()); + CHECK (array.begin () == array.end ()); + CHECK (array.empty ()); + + // push_back allocates + int j = 1; + array.push_back (j); + CHECK_EQUAL (1, array.size ()); + CHECK (array.capacity () > 0); + + // push_back(void) + int& i = array.push_back (); + i = 666; + CHECK_EQUAL(666, array.back ()); + + // clear frees memory? + array.clear (); + CHECK_EQUAL (0, array.size ()); + CHECK_EQUAL (0, array.capacity ()); + + // 3 item list + array.push_back (6); + array.push_back (7); + array.push_back (8); + + CHECK_EQUAL (3, array.size ()); + + // swapping + dynamic_array<int> ().swap (array); + CHECK_EQUAL (0, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // reserve + array.reserve (1024); + CHECK_EQUAL (1024, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // copy assignment + dynamic_array<int> array1; + j = 888; + array1.push_back (j); + + array = array1; + + CHECK_EQUAL (1, array.size ()); + CHECK_EQUAL (888, array.back ()); + CHECK_EQUAL (1, array1.size ()); + CHECK_EQUAL (888, array1.back ()); +} + + +TEST (DynamicArrayErase) +{ + dynamic_array<int> vs; + vs.resize_uninitialized(5); + + vs[0] = 0; + vs[1] = 1; + vs[2] = 2; + vs[3] = 3; + vs[4] = 4; + + vs.erase(vs.begin() + 1, vs.begin() + 4); + CHECK_EQUAL (2, vs.size()); + CHECK_EQUAL (5, vs.capacity()); + CHECK_EQUAL (0, vs[0]); + CHECK_EQUAL (4, vs[1]); +} + +static void VerifyConsecutiveIntArray (dynamic_array<int>& vs, int size, int capacity) +{ + CHECK_EQUAL (capacity, vs.capacity()); + CHECK_EQUAL (size, vs.size()); + for (int i=0;i<vs.size();i++) + CHECK_EQUAL (i, vs[i]); +} + +TEST (DynamicArrayInsertOnEmpty) +{ + dynamic_array<int> vs; + int vals[] = { 0, 1 }; + + vs.insert(vs.begin(), vals, vals + ARRAY_SIZE(vals)); + + VerifyConsecutiveIntArray(vs, 2, 2); +} + + +TEST (DynamicArrayInsert) +{ + dynamic_array<int> vs; + vs.resize_uninitialized(5); + + vs[0] = 0; + vs[1] = 1; + vs[2] = 4; + vs[3] = 5; + vs[4] = 6; + + int vals[] = { 2, 3 }; + + // inser two values + vs.insert(vs.begin() + 2, vals, vals + ARRAY_SIZE(vals)); + VerifyConsecutiveIntArray(vs, 7, 7); + + // empty insert + vs.insert(vs.begin() + 2, vals, vals); + + VerifyConsecutiveIntArray(vs, 7, 7); +} + +TEST (DynamicArrayResize) +{ + dynamic_array<int> vs; + vs.resize_initialized(3, 2); + CHECK_EQUAL (3, vs.capacity()); + CHECK_EQUAL (3, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); + CHECK_EQUAL (2, vs[2]); + + vs.resize_initialized(6, 3); + CHECK_EQUAL (6, vs.capacity()); + CHECK_EQUAL (6, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); + CHECK_EQUAL (2, vs[2]); + CHECK_EQUAL (3, vs[3]); + CHECK_EQUAL (3, vs[4]); + CHECK_EQUAL (3, vs[5]); + + vs.resize_initialized(5, 3); + CHECK_EQUAL (6, vs.capacity()); + CHECK_EQUAL (5, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); + CHECK_EQUAL (2, vs[2]); + CHECK_EQUAL (3, vs[3]); + CHECK_EQUAL (3, vs[4]); + + vs.resize_initialized(2, 3); + CHECK_EQUAL (6, vs.capacity()); + CHECK_EQUAL (2, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); +} + + +TEST(FixedSizeAllocator) +{ + typedef FixedSizeAllocator<sizeof(int)> TestAllocator; + + TestAllocator testalloc = TestAllocator(MemLabelId(kMemDefaultId, NULL)); + + CHECK(testalloc.capacity() == 0); + CHECK(testalloc.total_free() == 0); + CHECK(testalloc.total_allocated() == 0); + + int* ptr1 = (int*)testalloc.alloc(); + *ptr1 = 1; + + CHECK(testalloc.capacity() == 255*sizeof(int)); + CHECK(testalloc.total_free() == 254*sizeof(int)); + CHECK(testalloc.total_allocated() == sizeof(int)); + + int* ptr2 = (int*)testalloc.alloc(); + *ptr2 = 2; + + CHECK(testalloc.capacity() == 255*sizeof(int)); + CHECK(testalloc.total_free() == 253*sizeof(int)); + CHECK(testalloc.total_allocated() == 2*sizeof(int)); + CHECK(*ptr1==1); + CHECK(ptr1 + 1 == ptr2); + + testalloc.reset(); + + CHECK(testalloc.capacity() == 255*sizeof(int)); + CHECK(testalloc.total_free() == 255*sizeof(int)); + CHECK(testalloc.total_allocated() == 0); + + testalloc.free_memory(); + + CHECK(testalloc.capacity() == 0); + CHECK(testalloc.total_free() == 0); + CHECK(testalloc.total_allocated() == 0); +} + + + +TEST(StringFormatTest) +{ + CHECK_EQUAL ("Hello world it works", Format ("Hello %s it %s", "world", "works")); +} + + +#if !GAMERELEASE +TEST(UnityGUIDTest) +{ + UnityGUID identifier[5]; + identifier[0].Init (); + identifier[1].Init (); + identifier[2].Init (); + identifier[3].Init (); + identifier[4].Init (); + + CHECK (identifier[0] != identifier[1]); + CHECK (identifier[1] != identifier[2]); + CHECK (identifier[2] != identifier[3]); + CHECK (identifier[3] != identifier[4]); + identifier[0] = identifier[1]; + CHECK (identifier[0] == identifier[1]); +} +#endif + + +TEST (TestUtility) +{ + using namespace std; + + // Make sure that our stl implementation has consistent clear behaviour. + // If it doesn't we should probably stop using clear. + // + // If this test fails, it means that std::vector clear deallocates memory. + // Some optimized code this to not be the case! + + vector<int> test; + test.resize (10); + test.clear (); + CHECK (test.capacity () != 0); +} + +} +#endif diff --git a/Runtime/Utilities/VFPUtility.h b/Runtime/Utilities/VFPUtility.h new file mode 100644 index 0000000..e0176b9 --- /dev/null +++ b/Runtime/Utilities/VFPUtility.h @@ -0,0 +1,43 @@ +#ifndef VFPTILITY_H +#define VFPTILITY_H + +#if UNITY_SUPPORTS_VFP + +#define FMULS3(s0,s1,s2, s4,s5,s6, s8,s9,s10) \ + fmuls s##s0, s##s4, s##s8 ; \ + fmuls s##s1, s##s5, s##s9 ; \ + fmuls s##s2, s##s6, s##s10 + +#define FMACS3(s0,s1,s2, s4,s5,s6, s8,s9,s10) \ + fmacs s##s0, s##s4, s##s8 ; \ + fmacs s##s1, s##s5, s##s9 ; \ + fmacs s##s2, s##s6, s##s10 + +#define FCPYS3(s0,s1,s2, s4,s5,s6) \ + fcpys s##s0, s##s4 ; \ + fcpys s##s1, s##s5 ; \ + fcpys s##s2, s##s6 ; \ + + + +#define FMULS4(s0,s1,s2,s3, s4,s5,s6,s7, s8,s9,s10,s11) \ + fmuls s##s0, s##s4, s##s8 ; \ + fmuls s##s1, s##s5, s##s9 ; \ + fmuls s##s2, s##s6, s##s10 ;\ + fmuls s##s3, s##s7, s##s11 + +#define FMACS4(s0,s1,s2,s3, s4,s5,s6,s7, s8,s9,s10,s11) \ + fmacs s##s0, s##s4, s##s8 ; \ + fmacs s##s1, s##s5, s##s9 ; \ + fmacs s##s2, s##s6, s##s10 ;\ + fmacs s##s3, s##s7, s##s11 + +#define FCPYS4(s0,s1,s2,s3, s4,s5,s6,s7) \ + fcpys s##s0, s##s4 ; \ + fcpys s##s1, s##s5 ; \ + fcpys s##s2, s##s6 ; \ + fcpys s##s3, s##s7 ; \ + +#endif + +#endif diff --git a/Runtime/Utilities/ValidateArgs.h b/Runtime/Utilities/ValidateArgs.h new file mode 100644 index 0000000..7f50c66 --- /dev/null +++ b/Runtime/Utilities/ValidateArgs.h @@ -0,0 +1,81 @@ +#ifndef VALIDATEARGS_H +#define VALIDATEARGS_H + +#if DEBUGMODE + +#define ABORT_INVALID_FLOAT(value,varname,classname) \ +if (!IsFinite (value)) \ +{ \ + ErrorStringObject (Format ("%s.%s assign attempt for '%s' is not valid. Input %s is { %s }.", #classname, #varname, GetName (), #varname, FloatToString (value).c_str ()), this); \ + return; \ +} + +#define ABORT_INVALID_ARG_FLOAT(value,varname,methodname,classname) \ +if (!IsFinite (value)) \ +{ \ + ErrorStringObject (Format ("%s.%s(%s) assign attempt for '%s' is not valid. Input %s is { %s }.", #classname, #methodname, #varname, GetName (), #varname, FloatToString (value).c_str ()), this); \ + return; \ +} + +#ifdef VECTOR2_H +#define ABORT_INVALID_VECTOR2(value,varname,classname) \ +if (!IsFinite (value)) \ +{ \ + ErrorStringObject (Format ("%s.%s assign attempt for '%s' is not valid. Input %s is { %s, %s }.", #classname, #varname, GetName (), #varname, FloatToString (value.x).c_str (), FloatToString (value.y).c_str ()), this); \ + return; \ +} + +#define ABORT_INVALID_ARG_VECTOR2(value,varname,methodname,classname) \ +if (!IsFinite (value)) \ +{ \ + ErrorStringObject (Format ("%s.%s(%s) assign attempt for '%s' is not valid. Input %s is { %s, %s }.", #classname, #methodname, #varname, GetName (), #varname, FloatToString (value.x).c_str (), FloatToString (value.y).c_str ()), this); \ + return; \ +} +#endif + +#ifdef VECTOR3_H +#define ABORT_INVALID_VECTOR3(value,varname,classname) \ +if (!IsFinite (value)) \ +{ \ + ErrorStringObject (Format ("%s.%s assign attempt for '%s' is not valid. Input %s is { %s, %s, %s }.", #classname, #varname, GetName (), #varname, FloatToString (value.x).c_str (), FloatToString (value.y).c_str (), FloatToString (value.z).c_str ()), this); \ + return; \ +} + +#define ABORT_INVALID_ARG_VECTOR3(value,varname,methodname,classname) \ + if (!IsFinite (value)) \ +{ \ + ErrorStringObject (Format ("%s.%s(%s) assign attempt for '%s' is not valid. Input %s is { %s, %s, %s }.", #classname, #methodname, #varname, GetName (), #varname, FloatToString (value.x).c_str (), FloatToString (value.y).c_str (), FloatToString (value.z).c_str ()), this); \ + return; \ +} +#endif + +#ifdef QUATERNION_H +#define ABORT_INVALID_QUATERNION(value,varname,classname) \ +if (!IsFinite(value)) \ +{ \ + ErrorStringObject (Format("%s.%s assign attempt for '%s' is not valid. Input rotation is { %s, %s, %s, %s }.", #classname, #varname, GetName(), FloatToString(value.x).c_str(), FloatToString(value.y).c_str(), FloatToString(value.z).c_str(), FloatToString(value.w).c_str()), this); \ + return; \ +} + +#define ABORT_INVALID_ARG_QUATERNION(value,varname,methodname,classname) \ +if (!IsFinite(value)) \ +{ \ + ErrorStringObject (Format("%s.%s(%s) assign attempt for '%s' is not valid. Input rotation is { %s, %s, %s, %s }.", #classname, #methodname, #varname, GetName(), FloatToString(value.x).c_str(), FloatToString(value.y).c_str(), FloatToString(value.z).c_str(), FloatToString(value.w).c_str()), this); \ + return; \ +} +#endif + +#else + +#define ABORT_INVALID_FLOAT(value,varname,classname) +#define ABORT_INVALID_VECTOR2(value,varname,classname) +#define ABORT_INVALID_VECTOR3(value,varname,classname) +#define ABORT_INVALID_QUATERNION(value,varname,classname) + +#define ABORT_INVALID_ARG_FLOAT(value,varname,methodname,classname) +#define ABORT_INVALID_ARG_VECTOR2(value,varname,methodname,classname) +#define ABORT_INVALID_ARG_VECTOR3(value,varname,methodname,classname) +#define ABORT_INVALID_ARG_QUATERNION(value,varname,methodname,classname) + +#endif +#endif diff --git a/Runtime/Utilities/WavFileUtility.cpp b/Runtime/Utilities/WavFileUtility.cpp new file mode 100644 index 0000000..2d2471d --- /dev/null +++ b/Runtime/Utilities/WavFileUtility.cpp @@ -0,0 +1,54 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" + +#include "WavFileUtility.h" +#include <stdio.h> + +static inline int fput2 (unsigned short v, FILE* f) +{ + return fwrite (&v, 2, 1, f); +} + +static inline int fput4 (unsigned v, FILE* f) +{ + return fwrite (&v, 4, 1, f); +} + +bool WriteWaveFile (char const* filename, int channels, int samplebits, int freq, void* data, int samples) +{ + FILE* wf = fopen (filename, "wb+"); + if (!wf) + return false; + + size_t datasize = channels * (samplebits / 8) * samples; + +#define C(x) if (x < 0) goto err + + // RIFF chunk + C((fputs ("RIFF", wf))); + C((fput4 (datasize + (12-8) + 24 + 8, wf))); + C((fputs ("WAVE", wf))); + + // FORMAT chunk + C((fputs ("fmt ", wf))); + C((fput4 (0x10, wf))); + C((fput2 (0x01, wf))); + C((fput2 (channels, wf))); + C((fput4 (freq, wf))); + C((fput4 (channels * samplebits * freq, wf))); + C((fput2 (samplebits >> 3, wf))); + C((fput2 (samplebits, wf))); + + // DATA chunk + C((fputs ("data", wf))); + C((fput4 (datasize, wf))); + C((fwrite (data, datasize, 1, wf))); +#undef C + + fclose (wf); + return true; + +err: + fclose (wf); + return false; +} diff --git a/Runtime/Utilities/WavFileUtility.h b/Runtime/Utilities/WavFileUtility.h new file mode 100644 index 0000000..ec1c243 --- /dev/null +++ b/Runtime/Utilities/WavFileUtility.h @@ -0,0 +1,6 @@ +#ifndef UNITY_WAV_FILE_UTILITY_H_ +#define UNITY_WAV_FILE_UTILITY_H_ + +bool WriteWaveFile (char const* filename, int channels, int samplebits, int freq, void* data, int samples); + +#endif // UNITY_WAV_FILE_UTILITY_H_ diff --git a/Runtime/Utilities/Word.cpp b/Runtime/Utilities/Word.cpp new file mode 100644 index 0000000..2057283 --- /dev/null +++ b/Runtime/Utilities/Word.cpp @@ -0,0 +1,659 @@ +#include "UnityPrefix.h" +#include "Word.h" +#include <limits.h> +#include <stdio.h> +#include "Runtime/Math/FloatConversion.h" +#include "Annotations.h" + +#if !UNITY_EXTERNAL_TOOL +#include "Runtime/Allocator/LinearAllocator.h" +#endif + +using namespace std; + +bool BeginsWithCaseInsensitive (const std::string &s, const std::string &beginsWith) +{ + // Note that we don't want to convert the whole s string to lower case, simply because + // it might not be very efficient (imagine a string that has, e.g., 2 Mb chars), so we take + // only a substring that we need to compare with the given prefix. + return BeginsWith(ToLower(s.substr(0, beginsWith.length())), ToLower(beginsWith)); +} + +bool BeginsWith (const char* s, const char* beginsWith) +{ + return strncmp (s, beginsWith, strlen (beginsWith)) == 0; +} + + +#if !WEBPLUG +bool IsStringNumber (const string& s) +{ + return IsStringNumber (s.c_str ()); +} +#endif + + +#if UNITY_EDITOR +/* + Used by SemiNumericCompare: + if c[*index] is > '9', returns INT_MAX-265+c[*index] and increases *index by 1. + if '0' <= c[*index] <= '9' consumes all numeric characters and returns the numerical value + '0' + if c[*index] is < '0' returns (int)c[*index] and increases *index by 1; +*/ +static int GetSNOrdinal(const char* c, int& index) +{ + + if((unsigned char)c[index] < (unsigned char)'0') + return (unsigned char)c[index++]; + else if ((unsigned char)c[index] > (unsigned char)'9') + return (INT_MAX-256) + (unsigned char)ToLower(c[index++]); + else + { + int atoi=0; + while (c[index] >= '0' && c[index] <= '9') + { + atoi = atoi*10 + (c[index++] - '0'); // TODO: clamp at INT_MAX-256 + } + return atoi+'0'; + } +} + +// Human-like sorting. +// Sorts strings alphabetically, but with numbers in strings numerically, so "xx11" comes after "xx2". +int SemiNumericCompare(const char * str1, const char * str2) +{ + int i1 = 0; + int i2 = 0; + int o1, o2; + + while ((o1 = GetSNOrdinal(str1, i1)) == (o2 = GetSNOrdinal(str2, i2))) + { + if (!o1) + return i2-i1; // to make strings like "xx1", "xx01" and "xx001" have a stable sorting (longest string first as in Finder) + } + + return(o1 - o2); +} +#endif + +int StrNICmp(const char * str1, const char * str2, size_t n) +{ + const char * p1 = (char *) str1; + const char * p2 = (char *) str2; + char c1, c2; + size_t charsLeft=n; + + if( n <= 0 ) + return 0; + + while ((c1 = ToLower (*p1)) == (c2 = ToLower (*p2))) + { + ++p1; ++p2; --charsLeft; + if (!charsLeft || !c1) + return 0; + } + + return(c1 - c2); +} + +int StrICmp(const char * str1, const char * str2) +{ + const char * p1 = (char *) str1; + const char * p2 = (char *) str2; + char c1, c2; + + while ((c1 = ToLower (*p1)) == (c2 = ToLower (*p2))) + { + p1++; p2++; + if (!c1) + return 0; + } + + return(c1 - c2); +} + +int StrCmp(const char * str1, const char * str2) +{ + const char * p1 = (char *) str1; + const char * p2 = (char *) str2; + char c1, c2; + + while ((c1 = (*p1)) == (c2 = (*p2))) + { + p1++; p2++; + if (!c1) + return 0; + } + + return(c1 - c2); +} + + +#if !WEBPLUG +bool IsStringNumber (const char* s) +{ + bool success = false; + bool hadPoint = false; + long i = 0; + + while (s[i] != '\0') + { + switch (s[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + + success = true; + + break; + + case '.': + case ',': + + // Make sure we only have one . or , + if (hadPoint) + return false; + hadPoint = true; + + break; + + case '+': + case '-': + + // + or - are only allowed at the beginning of the string + if (i != 0) + return false; + break; + + default: + return false; + break; + } + i++; + } + + return success; +} +#endif + + +SInt32 StringToInt (char const* s) +{ + return atol (s); +} + +template<typename T> +inline string _ToString (const char* formatString, T value) +{ + char buf[255]; + + #ifdef WIN32 + _snprintf + #else + snprintf + #endif + (buf, sizeof(buf), formatString, value); + + return string (buf); +} + +string IntToString (SInt32 i) +{ + return _ToString ("%i", i); +} + +string UnsignedIntToString (UInt32 i) +{ + return _ToString ("%u", i); +} + +string Int64ToString (SInt64 i) +{ + return _ToString ("%lli", i); +} + +string UnsignedInt64ToString (UInt64 i) +{ + return _ToString ("%llu", i); +} + +string DoubleToString (double i) +{ + return _ToString ("%f", i); +} + +string FloatToString (float f, const char* precFormat) +{ + char buf[255]; + if (IsNAN(f)) + strcpy(buf, "NaN"); + else if (IsMinusInf(f)) + strcpy(buf, "-Infinity"); + else if (IsPlusInf(f)) + strcpy(buf, "Infinity"); + else + sprintf (buf, precFormat, f); + return string (buf); +} + +string SHA1ToString(unsigned char hash[20]) +{ + char buffer[41]; + for ( int i=0; i < 20; i++ ) + sprintf(&buffer[i*2], "%.2x", hash[i]); + return std::string(buffer, 40); +} + +string MD5ToString(unsigned char hash[16]) +{ + char buffer[33]; + for ( int i=0; i < 16; i++ ) + sprintf(&buffer[i*2], "%.2x", hash[i]); + return std::string(buffer, 32); +} + +// Parses simple float: optional sign, digits, period, digits. +// No exponent or leading whitespace support. +// No locale support. +// We use it to be independent of locale, and because atof() did +// show up in the shader parsing profile. +float SimpleStringToFloat (const char* str, int* outLength) +{ + const char *p = str; + + // optional sign + bool negative = false; + switch (*p) { + case '-': negative = true; // fall through to increment pointer + case '+': p++; + } + + double number = 0.0; + + // scan digits + while (IsDigit(*p)) + { + number = number * 10.0 + (*p - '0'); + p++; + } + + // optional decimal part + if (*p == '.') + { + p++; + + // scan digits after decimal point + double scaler = 0.1; + while (IsDigit(*p)) + { + number += (*p - '0') * scaler; + scaler *= 0.1; + p++; + } + } + + // apply sign + if (negative) + number = -number; + + // Do not check for equality with atof() - results + // of that are dependent on locale. + //DebugAssertIf(!CompareApproximately(number, atof(str))); + + if (outLength) + *outLength = (int) (p - str); + + return float(number); +} + + +void VFormatBuffer (char* buffer, int size, const char* format, va_list ap) +{ + va_list zp; + va_copy (zp, ap); + vsnprintf (buffer, size, format, zp); + va_end (zp); +} + +std::string VFormat (const char* format, va_list ap) +{ + va_list zp; + va_copy (zp, ap); + char buffer[1024 * 10]; + vsnprintf (buffer, 1024 * 10, format, zp); + va_end (zp); + return buffer; +} + +std::string Format (const char* format, ...) +{ + va_list va; + va_start (va, format); + std::string formatted = VFormat (format, va); + va_end (va); + return formatted; +} + +#if UNITY_EDITOR || UNITY_FBX_IMPORTER +bool AsciiToUTF8 (std::string& name) +{ + if (name.empty()) + return true; + + #if UNITY_OSX + CFStringRef str = CFStringCreateWithCString (NULL, name.c_str(), kCFStringEncodingASCII); + if (str != NULL) + { + bool res = false; + + char* tempName; + ALLOC_TEMP(tempName, char, name.size() * 2); + if (CFStringGetCString(str, tempName, name.size() * 2, kCFStringEncodingUTF8)) + { + name = tempName; + res = true; + } + CFRelease(str); + return res; + } + + return false; + + #elif UNITY_WIN + + bool result = false; + int bufferSize = (int) name.size()*4+1; + wchar_t* wideBuffer; + ALLOC_TEMP(wideBuffer, wchar_t, bufferSize); + if( MultiByteToWideChar( CP_ACP, 0, name.c_str(), -1, wideBuffer, bufferSize ) ) + { + char* buffer; + ALLOC_TEMP(buffer, char, bufferSize); + if( WideCharToMultiByte( CP_UTF8, 0, wideBuffer, -1, buffer, bufferSize, NULL, NULL ) ) + { + name = buffer; + result = true; + } + } + return result; + + #elif UNITY_LINUX + + return true; // do nothing for now. Tests should show how much is this + // function needed. + + #else + #error Unknown platform + #endif +} + +std::string StripInvalidIdentifierCharacters (std::string str) +{ + for (unsigned int i=0;i<str.size();i++) + { + char c = str[i]; + if (c == '~' || c == '&' || c == '%' || c == '|' || c == '$' || c == '<' || c == '>' || c == '/' || c == '\\') + str[i] = '_'; + } + return str; +} +#endif + +void HexStringToBytes (char* str, size_t bytes, void *data) +{ + for (size_t i=0; i<bytes; i++) + { + UInt8 b; + char ch = str[2*i+0]; + if (ch <= '9') + b = (ch - '0') << 4; + else + b = (ch - 'a' + 10) << 4; + + ch = str[2*i+1]; + if (ch <= '9') + b |= (ch - '0'); + else + b |= (ch - 'a' + 10); + + ((UInt8*)data)[i] = b; + } +} + +void BytesToHexString (const void *data, size_t bytes, char* str) +{ + static const char kHexToLiteral[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + for (size_t i=0; i<bytes;i++) + { + UInt8 b = ((UInt8*)data)[i]; + str[2*i+0] = kHexToLiteral[ b >> 4 ]; + str[2*i+1] = kHexToLiteral[ b & 0xf ]; + } +} + +std::string BytesToHexString (const void* data, size_t numBytes) +{ + std::string result; + result.resize (numBytes * 2); + BytesToHexString (data, numBytes, &result[0]); + return result; +} + +string FormatBytes(SInt64 b) +{ + AssertIf(sizeof(b) != 8); + + if(b < 0) + return "Unknown"; + if (b < 512) +#if UNITY_64 && UNITY_LINUX + return Format("%ld B",b); +#else + return Format("%lld B",b); +#endif + if (b < 512*1024) + return Format("%01.1f KB",b / 1024.0); + + b /= 1024; + if (b < 512*1024) + return Format("%01.1f MB", b / 1024.0); + + b /= 1024; + return Format("%01.2f GB",b / 1024.0); +} + +std::string Append (char const* a, std::string const& b) +{ + std::string r; + size_t asz = strlen (a); + r.reserve (asz + b.size ()); + r.assign (a, asz); + r.append (b); + return r; +} + +std::string Append (char const* a, char const* b) +{ + std::string r; + size_t asz = strlen (a); + size_t bsz = strlen (b); + r.reserve (asz + bsz); + r.assign (a, asz); + r.append (b, bsz); + return r; +} + +std::string Append (std::string const& a, char const* b) +{ + std::string r; + size_t bsz = strlen(b); + r.reserve(a.size() + bsz); + r.assign(a); + r.append(b, bsz); + return r; +} + +#if UNITY_OSX || UNITY_IPHONE +CFStringRef StringToCFString (const std::string &str) +{ + return CFStringCreateWithCString(kCFAllocatorDefault, str.c_str(), kCFStringEncodingUTF8); +} + +std::string CFStringToString (CFStringRef str) +{ + std::string output; + if (str) + { + int bufferSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8) + 1; + char *buf; + MALLOC_TEMP( buf, bufferSize ); + if (CFStringGetCString(str, buf, bufferSize, kCFStringEncodingUTF8)) + output = buf; + } + return output; +} +#endif + +std::string Trim(const std::string &input, const std::string &ws) +{ + size_t startpos = input.find_first_not_of(ws); // Find the first character position after excluding leading blank spaces + size_t endpos = input.find_last_not_of(ws); // Find the first character position from reverse af + + // if all spaces or empty return an empty string + if(( string::npos == startpos ) || ( string::npos == endpos)) + { + return std::string(); // empty string + } + else + { + return input.substr( startpos, endpos-startpos+1 ); + } +} + + +/* + Convert version string of form XX.X.XcXXX to UInt32 in Apple 'vers' representation + as described in Tech Note TN1132 (http://developer.apple.com/technotes/tn/pdf/tn1132.pdf) + eg. + 1.2.0 + 1.2.1 + 1.2.1a1 + 1.2.1b1 + 1.2.1r12 + */ +int GetNumericVersion (char const* versionCString) +{ + if( (*versionCString)=='\0' ) + return 0; + + int major=0,minor=0,fix=0,type='r',release=0; + const char *spos=versionCString; + major = *(spos++) - '0'; + if (*spos>='0' && *spos < '9') + major = major*10 + *(spos++)-'0'; + + if (*spos) + { + if (*spos=='.') + { + spos++; + if(*spos) + minor=*(spos++)-'0'; + } + if (*spos) + { + if (*spos=='.') + { + spos++; + if (*spos) + fix=*(spos++)-'0'; + } + if (*spos) + { + type = *(spos++); + if (*spos) + { + release = *(spos++)-'0'; + if (*spos) + { + release = release*10+*(spos++)-'0'; + if (*spos) + { + release=release*10+*(spos++)-'0'; + } + } + } + } + } + } + + unsigned int version = 0; + version |= ((major/10)%10)<<28; + version |= (major%10)<<24; + version |= (minor%10)<<20; + version |= (fix%10)<<16; + switch( type ) + { + case 'D': + case 'd': version |= 0x2<<12; break; + case 'A': + case 'a': version |= 0x4<<12; break; + case 'B': + case 'b': version |= 0x6<<12; break; + case 'F': + case 'R': + case 'f': + case 'r': version |= 0x8<<12; break; + } + version |= ((release / 100)%10)<<8; + version |= ((release / 10) %10)<<4; + version |= release % 10; + + AssertIf (((int)version) < 0); + + return version; +} + +void Split (const std::string s, char splitChar, std::vector<std::string> &parts) +{ + int n = 0, n1 = 0; + while ( 1 ) + { + n1 = s.find (splitChar, n); + std::string p = s.substr (n, n1-n); + if ( p.length () ) + { + parts.push_back (p); + } + if ( n1 == std::string::npos ) + break; + + n = n1 + 1; + } +} + +void Split (const std::string s, const char* splitChars, std::vector<std::string> &parts) +{ + int n = 0, n1 = 0; + while ( 1 ) + { + n1 = s.find_first_of (splitChars, n); + std::string p = s.substr (n, n1-n); + if ( p.length () ) + { + parts.push_back (p); + } + if ( n1 == std::string::npos ) + break; + + n = n1 + 1; + } +} diff --git a/Runtime/Utilities/Word.h b/Runtime/Utilities/Word.h new file mode 100644 index 0000000..d54ac5b --- /dev/null +++ b/Runtime/Utilities/Word.h @@ -0,0 +1,227 @@ +#ifndef WORD_H +#define WORD_H + +#include <string> +#include <vector> +#include <stdarg.h> // va_list +#include <vector> +#include "Annotations.h" +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Modules/ExportModules.h" + +#if UNITY_OSX || UNITY_IPHONE + #include "CoreFoundation/CoreFoundation.h" +#endif + +bool BeginsWithCaseInsensitive (const std::string &s, const std::string &beginsWith); + +bool BeginsWith (const char* s, const char* beginsWith); + +template<typename StringType> +bool BeginsWith (const StringType &s, const StringType &beginsWith) +{ + return BeginsWith (s.c_str(), beginsWith.c_str()); +} +template<typename StringType> +bool BeginsWith (const StringType &s, const char *beginsWith) +{ + return BeginsWith (s.c_str(), beginsWith); +} + +inline bool EndsWith (const char* str, int strLen, const char* sub, int subLen){ + return (strLen >= subLen) && (strncmp (str+strLen-subLen, sub, subLen)==0); +} + +template<typename StringType> +bool EndsWith (const StringType& str, const StringType& sub) +{ + return EndsWith(str.c_str(), str.size(), sub.c_str(), sub.size()); +} + +template<typename StringType> +bool EndsWith (const StringType& str, const char* endsWith) +{ + return EndsWith(str.c_str(), str.size(), endsWith, strlen(endsWith)); +} + +inline bool EndsWith (const char* s, const char* endsWith){ + return EndsWith(s, strlen(s), endsWith, strlen(endsWith)); +} + +void ConcatCString( char* root, const char* append ); +#if !WEBPLUG +bool IsStringNumber (const char* s); +bool IsStringNumber (const std::string& s); +#endif + +SInt32 StringToInt (char const* s); + +template <typename StringType> +SInt32 StringToInt (const StringType& s) +{ + return StringToInt(s.c_str()); +} + +/// Replacement for atof is not dependent on locale settings for what to use as the decimal separator. +/// Limited support but fast. It does'nt work for infinity, nan, but +/// This function is lossy. Converting a string back and forth does not result in the same binary exact float representation. +/// See FloatStringConversion.h for binary exact string<->float conversion functions. +float SimpleStringToFloat (const char* str, int* outLength = NULL); + +std::string IntToString (SInt32 i); +std::string UnsignedIntToString (UInt32 i); +std::string Int64ToString (SInt64 i); +std::string UnsignedInt64ToString (UInt64 i); +std::string DoubleToString (double i); +std::string EXPORT_COREMODULE FloatToString (float f, const char* precFormat = "%f"); + +std::string SHA1ToString(unsigned char hash[20]); +std::string MD5ToString(unsigned char hash[16]); + +int StrNICmp (const char* a, const char* b, size_t n); +int StrICmp (const char* a, const char* b); +inline int StrICmp(const UnityStr& str1, const UnityStr& str2) { return StrICmp (str1.c_str (), str2.c_str ()); } +int StrCmp (const char* a, const char* b); +inline int StrCmp(const std::string& str1, const std::string& str2) { return StrCmp (str1.c_str (), str2.c_str ()); } +inline int StrICmp(const std::string& str1, const std::string& str2) { return StrICmp (str1.c_str (), str2.c_str ()); } + +#if UNITY_EDITOR +int SemiNumericCompare(const char * str1, const char * str2); +inline int SemiNumericCompare(const std::string& str1, const std::string& str2) { return SemiNumericCompare (str1.c_str (), str2.c_str ()); } +#endif + +inline char ToLower (char v) +{ + if (v >= 'A' && v <= 'Z') + return static_cast<char>(v | 0x20); + else + return v; +} + +inline char ToUpper (char v) +{ + if (v >= 'a' && v <= 'z') + return static_cast<char>(v & 0xdf); + else + return v; +} + +template<typename StringType> +StringType ToUpper (const StringType& input) +{ + StringType s = input; + for (typename StringType::iterator i= s.begin (); i != s.end ();i++) + *i = ToUpper (*i); + return s; +} +template<typename StringType> +StringType ToLower (const StringType& input) +{ + StringType s = input; + for (typename StringType::iterator i= s.begin (); i != s.end ();i++) + *i = ToLower (*i); + return s; +} +template<typename StringType> +void ToUpperInplace (StringType& input) +{ + for (typename StringType::iterator i= input.begin (); i != input.end ();i++) + *i = ToUpper (*i); +} +template<typename StringType> +void ToLowerInplace (StringType& input) +{ + for (typename StringType::iterator i= input.begin (); i != input.end ();i++) + *i = ToLower (*i); +} + +TAKES_PRINTF_ARGS(1,2) std::string EXPORT_COREMODULE Format (const char* format, ...); +std::string VFormat (const char* format, va_list ap); + +void VFormatBuffer (char* buffer, int size, const char* format, va_list ap); +template<typename StringType> +inline TAKES_PRINTF_ARGS(1,2) StringType FormatString(const char* format, ...) +{ + char buffer[10*1024]; + va_list va; + va_start( va, format ); + VFormatBuffer (buffer, 10*1024, format, va); + return StringType(buffer); +} + +std::string Append (char const* a, std::string const& b); +EXPORT_COREMODULE std::string Append (char const* a, char const* b); +std::string Append (std::string const& a, char const* b); + +inline bool IsDigit (char c) { return c >= '0' && c <= '9'; } +inline bool IsAlpha (char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +inline bool IsSpace (char c) { return c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r' || c == ' '; } +inline bool IsAlphaNumeric (char c) { return IsDigit (c) || IsAlpha (c); } + +template<typename alloc> +void replace_string (std::basic_string<char, std::char_traits<char>, alloc>& target, + const std::basic_string<char, std::char_traits<char>, alloc>& search, + const std::basic_string<char, std::char_traits<char>, alloc>& replace, size_t startPos = 0) +{ + if (search.empty()) + return; + + typename std::basic_string<char, std::char_traits<char>, alloc>::size_type p = startPos; + while ((p = target.find (search, p)) != std::basic_string<char, std::char_traits<char>, alloc>::npos) + { + target.replace (p, search.size (), replace); + p += replace.size (); + } +} + +template<typename StringType> +void replace_string (StringType& target, const char* search, const StringType& replace, size_t startPos = 0) +{ + replace_string(target,StringType(search),replace, startPos); +} +template<typename StringType> +void replace_string (StringType& target, const StringType& search, const char* replace, size_t startPos = 0) +{ + replace_string(target,search,StringType(replace), startPos); +} +template<typename StringType> +void replace_string (StringType& target, const char* search, const char* replace, size_t startPos = 0) +{ + replace_string(target,StringType(search),StringType(replace), startPos); +} + +#if UNITY_EDITOR || UNITY_FBX_IMPORTER +/// Converts name to UTF8. Returns whether conversion was successful. +/// If not successful name will not be touched +bool AsciiToUTF8 (std::string& name); +std::string StripInvalidIdentifierCharacters (std::string str); +#endif + +std::string FormatBytes(SInt64 b); + +#if UNITY_OSX || UNITY_IPHONE +CFStringRef StringToCFString (const std::string &str); +std::string CFStringToString (CFStringRef str); +#endif + +std::string Trim(const std::string &input, const std::string &ws=" \t"); + +void HexStringToBytes (char* str, size_t numBytes, void *data); +void BytesToHexString (const void *data, size_t numBytes, char* str); +std::string BytesToHexString (const void* data, size_t numBytes); + +int GetNumericVersion (char const* versionCString); +inline int GetNumericVersion (const std::string& versionString) { return GetNumericVersion (versionString.c_str()); } + +/// Split a string delimited by splitChar or any character in splitChars into parts. +/// Parts is appended, not cleared. +/// Empty parts are discarded. +void Split (const std::string s, char splitChar, std::vector<std::string> &parts); +void Split (const std::string s, const char* splitChars, std::vector<std::string> &parts); + +inline std::string QuoteString( const std::string& str ) +{ + return '"' + str + '"'; +} + +#endif diff --git a/Runtime/Utilities/WordTests.cpp b/Runtime/Utilities/WordTests.cpp new file mode 100644 index 0000000..5d02ba2 --- /dev/null +++ b/Runtime/Utilities/WordTests.cpp @@ -0,0 +1,151 @@ +#include "UnityPrefix.h" + +#if ENABLE_UNIT_TESTS + +#include "Word.h" +#include "Runtime/Testing/Testing.h" + +using namespace std; + +SUITE (WordTests) +{ + TEST (IntToString_Works) + { + CHECK (IntToString (123456) == "123456"); + CHECK (IntToString (-123456) == "-123456"); + } + + TEST (Int64ToString_Works) + { + CHECK (Int64ToString (1099511627776) == "1099511627776"); + CHECK (Int64ToString (-1099511627776) == "-1099511627776"); + } + + TEST (UnsignedIntToString_Works) + { + CHECK (IntToString (123456) == "123456"); + } + + TEST (UnsignedInt64ToString_Works) + { + CHECK (UnsignedInt64ToString (1099511627776) == "1099511627776"); + } + + TEST (Word_EndsWith) + { + CHECK(EndsWith("abc","c")); + CHECK(EndsWith("abc","bc")); + CHECK(EndsWith("abc","abc")); + CHECK(EndsWith("abc","")); + CHECK(!EndsWith("abc","d")); + CHECK(!EndsWith("abc","abcd")); + } + + TEST (Word_IsStringNumber) + { + CHECK_EQUAL(true, IsStringNumber ("-1")); + CHECK_EQUAL(true, IsStringNumber ("+2")); + CHECK_EQUAL(false, IsStringNumber ("2+")); + CHECK_EQUAL(false, IsStringNumber ("a")); + CHECK_EQUAL(false, IsStringNumber ("1b")); + } + + TEST (Word_ReplaceString) + { + string s; + s = "foo bar foo"; replace_string (s, "foo", "x"); CHECK_EQUAL("x bar x", s); + s = "foo bar foo"; replace_string (s, "", ""); CHECK_EQUAL("foo bar foo", s); + } + + TEST (Word_SimpleStringToFloatWorks) + { + int len; + CHECK_EQUAL (0.0f, SimpleStringToFloat("0",&len)); CHECK_EQUAL(1,len); + CHECK_EQUAL (0.0f, SimpleStringToFloat("0.0",&len)); CHECK_EQUAL(3,len); + CHECK_EQUAL (0.0f, SimpleStringToFloat(".0",&len)); CHECK_EQUAL(2,len); + CHECK_EQUAL (12.05f, SimpleStringToFloat("12.05",&len)); CHECK_EQUAL(5,len); + CHECK_EQUAL (-3.5f, SimpleStringToFloat("-3.5",&len)); CHECK_EQUAL(4,len); + CHECK_EQUAL (3.14f, SimpleStringToFloat("3.14",&len)); CHECK_EQUAL(4,len); + CHECK_EQUAL (-1024.5f, SimpleStringToFloat("-1024.500",&len)); CHECK_EQUAL(9,len); + } + + TEST (Word_Trim) + { + string s; + s=Trim(" \tspaces in front\n"); CHECK_EQUAL("spaces in front\n",s); + s=Trim("spaces behind \t \t\t"); CHECK_EQUAL("spaces behind",s); + s=Trim("\t\t\t\tspaces at both ends \t \t\t"); CHECK_EQUAL("spaces at both ends",s); + s=Trim(""); CHECK_EQUAL("",s); + s=Trim("\t\t\t \t \t"); CHECK_EQUAL("",s); + s=Trim("\n\n Custom Whitespace\r\n","\r\n"); CHECK_EQUAL(" Custom Whitespace",s); + } + + TEST (Word_Split) + { + const int kNumtests = 6; + const int kMaxtokens = 3; + const char splitChar = ';'; + const char* splitChars = ";/"; + + string inputs[kNumtests] = + { + "Normal;string;split", + "Adjacent;;separators", + "NoSeparators", + "EndWithSeparator;", + ";StartWithSeparator", + ";" // No non-separators + }; + + string inputsMulti[kNumtests] = + { + "Normal;string/split", + "Adjacent/;separators", + "NoSeparators", + "EndWithSeparator;/", + ";StartWithSeparator", + ";" // No non-separators + }; + + int outputSizes[kNumtests] = + { + 3, 2, 1, 1, 1, 0 + }; + + string outputTokens[][kMaxtokens] = + { + { "Normal", "string", "split" }, + { "Adjacent", "separators", "" }, + { "NoSeparators", "", "" }, + { "EndWithSeparator", "", "" }, + { "StartWithSeparator", "", "" }, + { "", "", "" } + }; + + for (int test = 0; test < kNumtests; ++test) + { + string s = inputs[test]; + vector<string> tokens; + Split (inputs[test], splitChar, tokens); + CHECK_EQUAL (outputSizes[test], tokens.size ()); // Verify number of tokens + for (int token = 0; token < outputSizes[test]; ++token) + { + CHECK_EQUAL (outputTokens[test][token], tokens[token]); // Verify each token + } + } + + for (int test = 0; test < kNumtests; ++test) + { + string s = inputs[test]; + vector<string> tokens; + Split (inputsMulti[test], splitChars, tokens); + CHECK_EQUAL (outputSizes[test], tokens.size ()); // Verify number of tokens + for (int token = 0; token < outputSizes[test]; ++token) + { + CHECK_EQUAL (outputTokens[test][token], tokens[token]); // Verify each token + } + } + } +} + +#endif // ENABLE_UNIT_TESTS diff --git a/Runtime/Utilities/algorithm_utility.h b/Runtime/Utilities/algorithm_utility.h new file mode 100644 index 0000000..1c56bb1 --- /dev/null +++ b/Runtime/Utilities/algorithm_utility.h @@ -0,0 +1,115 @@ +#ifndef ALGORITHM_UTILITY_H +#define ALGORITHM_UTILITY_H + +#include <algorithm> +#include <functional> +#include "LogAssert.h" + +template<class T, class Func> +void repeat (T& type, int count, Func func) +{ + int i; + for (i=0;i<count;i++) + func (type); +} + +template<class C, class Func> +Func for_each (C& c, Func f) +{ + return for_each (c.begin (), c.end (), f); +} + +template<class C, class Func> +void erase_if (C& c, Func f) +{ + c.erase (remove_if (c.begin (), c.end (), f), c.end ()); +} + +template<class C, class T> +void erase (C& c, const T& t) +{ + c.erase (remove (c.begin (), c.end (), t), c.end ()); +} + +template<class C, class T> +typename C::iterator find (C& c, const T& value) +{ + return find (c.begin (), c.end (), value); +} + +template<class C, class Pred> +typename C::iterator find_if (C& c, Pred p) +{ + return find_if (c.begin (), c.end (), p); +} + +template <class T, class U> +struct EqualTo + : std::binary_function<T, U, bool> +{ + bool operator()(const T& x, const U& y) const { return static_cast<bool>(x == y); } +}; + +// Returns the iterator to the last element +template<class Container> +typename Container::iterator last_iterator (Container& container) +{ + AssertIf (container.begin () == container.end ()); + typename Container::iterator i = container.end (); + i--; + return i; +} + +template<class Container> +typename Container::const_iterator last_iterator (const Container& container) +{ + AssertIf (container.begin () == container.end ()); + typename Container::const_iterator i = container.end (); + i--; + return i; +} + + +// Efficient "add or update" for STL maps. +// For more details see item 24 on Effective STL. +// Basically it avoids constructing default value only to +// assign it later. +template<typename MAP, typename K, typename V> +bool add_or_update( MAP& m, const K& key, const V& val ) +{ + typename MAP::iterator lb = m.lower_bound( key ); + if( lb != m.end() && !m.key_comp()( key, lb->first ) ) + { + // lb points to a pair with the given key, update pair's value + lb->second = val; + return false; + } + else + { + // no key exists, insert new pair + m.insert( lb, std::make_pair(key,val) ); + return true; + } +} + +template<class ForwardIterator> +bool is_sorted (ForwardIterator begin, ForwardIterator end) +{ + for (ForwardIterator next = begin; begin != end && ++next != end; ++begin) + if (*next < *begin) + return false; + + return true; +} + +template<class ForwardIterator, class Predicate> +bool is_sorted (ForwardIterator begin, ForwardIterator end, Predicate pred) +{ + for (ForwardIterator next = begin; begin != end && ++next != end; ++begin) + if (pred(*next, *begin)) + return false; + + return true; +} + +#endif diff --git a/Runtime/Utilities/delayed_set.h b/Runtime/Utilities/delayed_set.h new file mode 100644 index 0000000..cdb8fd1 --- /dev/null +++ b/Runtime/Utilities/delayed_set.h @@ -0,0 +1,45 @@ +#ifndef DELAYED_SET_H +#define DELAYED_SET_H + +#include <set> +#include <vector> +#include "MemoryPool.h" + +template <class T, class SetType = std::set<T, std::less<T> , memory_pool<T> > > +class delayed_set : public SetType { + typedef typename std::vector<std::pair<bool, T> > delay_container; + delay_container m_Delayed; + + public: + + bool is_inserted (const T& object) + { + typename delay_container::reverse_iterator i; + for (i = m_Delayed.rbegin ();i != m_Delayed.rend ();i++) + { + if (i->second == object) + return i->first; + } + return this->find (object) != this->end (); + } + + void remove_delayed (const T& obj) { + m_Delayed.push_back (std::pair<bool, T> (false, obj)); + } + void add_delayed (const T& obj) { + m_Delayed.push_back (std::pair<bool, T> (true, obj)); + } + int apply_delayed_size () const { return m_Delayed.size (); } + void apply_delayed () { + typename delay_container::iterator iter; + for (iter = m_Delayed.begin(); iter != m_Delayed.end(); iter++) { + if (iter->first) + SetType::insert (iter->second); + else + SetType::erase (iter->second); + } + m_Delayed.clear (); + } +}; + +#endif diff --git a/Runtime/Utilities/dense_hash_map.h b/Runtime/Utilities/dense_hash_map.h new file mode 100644 index 0000000..a45000e --- /dev/null +++ b/Runtime/Utilities/dense_hash_map.h @@ -0,0 +1,243 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ---- +// Author: Craig Silverstein +// +// This is just a very thin wrapper over densehashtable.h, just +// like sgi stl's stl_hash_map is a very thin wrapper over +// stl_hashtable. The major thing we define is operator[], because +// we have a concept of a data_type which stl_hashtable doesn't +// (it only has a key and a value). +// +// NOTE: this is exactly like sparse_hash_map.h, with the word +// "sparse" replaced by "dense", except for the addition of +// set_empty_key(). +// +// YOU MUST CALL SET_EMPTY_KEY() IMMEDIATELY AFTER CONSTRUCTION. +// +// Otherwise your program will die in mysterious ways. +// +// In other respects, we adhere mostly to the STL semantics for +// hash-map. One important exception is that insert() invalidates +// iterators entirely. On the plus side, though, erase() doesn't +// invalidate iterators at all, or even change the ordering of elements. +// +// Here are a few "power user" tips: +// +// 1) set_deleted_key(): +// If you want to use erase() you must call set_deleted_key(), +// in addition to set_empty_key(), after construction. +// The deleted and empty keys must differ. +// +// 2) resize(0): +// When an item is deleted, its memory isn't freed right +// away. This allows you to iterate over a hashtable, +// and call erase(), without invalidating the iterator. +// To force the memory to be freed, call resize(0). +// +// Guide to what kind of hash_map to use: +// (1) dense_hash_map: fastest, uses the most memory +// (2) sparse_hash_map: slowest, uses the least memory +// (3) hash_map (STL): in the middle +// Typically I use sparse_hash_map when I care about space and/or when +// I need to save the hashtable on disk. I use hash_map otherwise. I +// don't personally use dense_hash_map ever; the only use of +// dense_hash_map I know of is to work around malloc() bugs in some +// systems (dense_hash_map has a particularly simple allocation scheme). +// +// - dense_hash_map has, typically, a factor of 2 memory overhead (if your +// data takes up X bytes, the hash_map uses X more bytes in overhead). +// - sparse_hash_map has about 2 bits overhead per entry. +// - sparse_hash_map can be 3-7 times slower than the others for lookup and, +// especially, inserts. See time_hash_map.cc for details. +// +// See /usr/(local/)?doc/sparsehash-0.1/dense_hash_map.html +// for information about how to use this class. + +#ifndef _DENSE_HASH_MAP_H_ +#define _DENSE_HASH_MAP_H_ + +//#include <google/sparsehash/sparseconfig.h> +#include <stdio.h> // for FILE * in read()/write() +#include <algorithm> // for the default template args +#include <functional> // for equal_to +#include <memory> // for alloc<> +#include <utility> // for pair<> +//#include <ext/hash_fun.h> // defined in config.h +#include "densehashtable.h" + + +using std::pair; + +template <class Key, class T, + class HashFcn, + class EqualKey = std::equal_to<Key>, + class Alloc = std::allocator< std::pair<const Key, T> > > +class dense_hash_map { + private: + // Apparently select1st is not stl-standard, so we define our own + struct SelectKey { + const Key& operator()(const pair<const Key, T>& p) const { + return p.first; + } + }; + + // The actual data + typedef dense_hashtable<pair<const Key, T>, Key, HashFcn, + SelectKey, EqualKey, Alloc> ht; + ht rep; + + public: + typedef typename ht::key_type key_type; + typedef T data_type; + typedef T mapped_type; + typedef typename ht::value_type value_type; + typedef typename ht::hasher hasher; + typedef typename ht::key_equal key_equal; + + typedef typename ht::size_type size_type; + typedef typename ht::difference_type difference_type; + typedef typename ht::pointer pointer; + typedef typename ht::const_pointer const_pointer; + typedef typename ht::reference reference; + typedef typename ht::const_reference const_reference; + + typedef typename ht::iterator iterator; + typedef typename ht::const_iterator const_iterator; + + // Iterator functions + iterator begin() { return rep.begin(); } + iterator end() { return rep.end(); } + const_iterator begin() const { return rep.begin(); } + const_iterator end() const { return rep.end(); } + + + // Accessor functions + hasher hash_funct() const { return rep.hash_funct(); } + key_equal key_eq() const { return rep.key_eq(); } + + + // Constructors + explicit dense_hash_map(size_type n = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal()) + : rep(n, hf, eql) { } + + template <class InputIterator> + dense_hash_map(InputIterator f, InputIterator l, + size_type n = 0, + const hasher& hf = hasher(), + const key_equal& eql = key_equal()) + : rep(n, hf, eql) { + rep.insert(f, l); + } + // We use the default copy constructor + // We use the default operator=() + // We use the default destructor + + void clear() { rep.clear(); } + // This clears the hash map without resizing it down to the minimum + // bucket count, but rather keeps the number of buckets constant + void clear_no_resize() { rep.clear_no_resize(); } + void swap(dense_hash_map& hs) { rep.swap(hs.rep); } + + + // Functions concerning size + size_type size() const { return rep.size(); } + size_type max_size() const { return rep.max_size(); } + bool empty() const { return rep.empty(); } + size_type bucket_count() const { return rep.bucket_count(); } + size_type max_bucket_count() const { return rep.max_bucket_count(); } + + void resize(size_type hint) { rep.resize(hint); } + + + // Lookup routines + iterator find(const key_type& key) { return rep.find(key); } + const_iterator find(const key_type& key) const { return rep.find(key); } + + data_type& operator[](const key_type& key) { // This is our value-add! + iterator it = find(key); + if (it != end()) { + return it->second; + } else { + return insert(value_type(key, data_type())).first->second; + } + } + + size_type count(const key_type& key) const { return rep.count(key); } + + pair<iterator, iterator> equal_range(const key_type& key) { + return rep.equal_range(key); + } + pair<const_iterator, const_iterator> equal_range(const key_type& key) const { + return rep.equal_range(key); + } + + // Insertion routines + pair<iterator, bool> insert(const value_type& obj) { return rep.insert(obj); } + template <class InputIterator> + void insert(InputIterator f, InputIterator l) { rep.insert(f, l); } + void insert(const_iterator f, const_iterator l) { rep.insert(f, l); } + // required for std::insert_iterator; the passed-in iterator is ignored + iterator insert(iterator, const value_type& obj) { return insert(obj).first; } + + + // Deletion and empty routines + // THESE ARE NON-STANDARD! I make you specify an "impossible" key + // value to identify deleted and empty buckets. You can change the + // deleted key as time goes on, or get rid of it entirely to be insert-only. + void set_empty_key(const key_type& key) { // YOU MUST CALL THIS! + rep.set_empty_key(value_type(key, data_type())); // rep wants a value + } + void set_deleted_key(const key_type& key) { + rep.set_deleted_key(value_type(key, data_type())); // rep wants a value + } + void clear_deleted_key() { rep.clear_deleted_key(); } + + // These are standard + size_type erase(const key_type& key) { return rep.erase(key); } + void erase(iterator it) { rep.erase(it); } + void erase(iterator f, iterator l) { rep.erase(f, l); } + + + // Comparison + bool operator==(const dense_hash_map& hs) const { return rep == hs.rep; } + bool operator!=(const dense_hash_map& hs) const { return rep != hs.rep; } +}; + +// We need a global swap as well +template <class Key, class T, class HashFcn, class EqualKey, class Alloc> +inline void swap(dense_hash_map<Key, T, HashFcn, EqualKey, Alloc>& hm1, + dense_hash_map<Key, T, HashFcn, EqualKey, Alloc>& hm2) { + hm1.swap(hm2); +} + +#endif /* _DENSE_HASH_MAP_H_ */ diff --git a/Runtime/Utilities/densehashtable.h b/Runtime/Utilities/densehashtable.h new file mode 100644 index 0000000..763bce1 --- /dev/null +++ b/Runtime/Utilities/densehashtable.h @@ -0,0 +1,899 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// --- +// Author: Craig Silverstein +// +// A dense hashtable is a particular implementation of +// a hashtable: one that is meant to minimize memory allocation. +// It does this by using an array to store all the data. We +// steal a value from the key space to indicate "empty" array +// elements (ie indices where no item lives) and another to indicate +// "deleted" elements. +// +// (Note it is possible to change the value of the delete key +// on the fly; you can even remove it, though after that point +// the hashtable is insert_only until you set it again. The empty +// value however can't be changed.) +// +// To minimize allocation and pointer overhead, we use internal +// probing, in which the hashtable is a single table, and collisions +// are resolved by trying to insert again in another bucket. The +// most cache-efficient internal probing schemes are linear probing +// (which suffers, alas, from clumping) and quadratic probing, which +// is what we implement by default. +// +// Type requirements: value_type is required to be Copy Constructible +// and Default Constructible. It is not required to be (and commonly +// isn't) Assignable. +// +// You probably shouldn't use this code directly. Use +// <google/dense_hash_map> or <google/dense_hash_set> instead. + +// You can change the following below: +// HT_OCCUPANCY_FLT -- how full before we double size +// HT_EMPTY_FLT -- how empty before we halve size +// HT_MIN_BUCKETS -- default smallest bucket size +// +// How to decide what values to use? +// HT_EMPTY_FLT's default of .4 * OCCUPANCY_FLT, is probably good. +// HT_MIN_BUCKETS is probably unnecessary since you can specify +// (indirectly) the starting number of buckets at construct-time. +// For HT_OCCUPANCY_FLT, you can use this chart to try to trade-off +// expected lookup time to the space taken up. By default, this +// code uses quadratic probing, though you can change it to linear +// via _JUMP below if you really want to. +// +// From http://www.augustana.ca/~mohrj/courses/1999.fall/csc210/lecture_notes/hashing.html +// NUMBER OF PROBES / LOOKUP Successful Unsuccessful +// Quadratic collision resolution 1 - ln(1-L) - L/2 1/(1-L) - L - ln(1-L) +// Linear collision resolution [1+1/(1-L)]/2 [1+1/(1-L)2]/2 +// +// -- HT_OCCUPANCY_FLT -- 0.10 0.50 0.60 0.75 0.80 0.90 0.99 +// QUADRATIC COLLISION RES. +// probes/successful lookup 1.05 1.44 1.62 2.01 2.21 2.85 5.11 +// probes/unsuccessful lookup 1.11 2.19 2.82 4.64 5.81 11.4 103.6 +// LINEAR COLLISION RES. +// probes/successful lookup 1.06 1.5 1.75 2.5 3.0 5.5 50.5 +// probes/unsuccessful lookup 1.12 2.5 3.6 8.5 13.0 50.0 5000.0 + +#ifndef _DENSEHASHTABLE_H_ +#define _DENSEHASHTABLE_H_ + +// The probing method +// Linear probing +// #define JUMP_(key, num_probes) ( 1 ) +// Quadratic-ish probing +#define JUMP_(key, num_probes) ( num_probes ) + + +// Hashtable class, used to implement the hashed associative containers +// hash_set and hash_map. + +//#include <google/sparsehash/sparseconfig.h> +//#include <Assert.h> +#include "LogAssert.h" +#include <stdlib.h> // for abort() +#include <algorithm> // For swap(), eg +#include <iostream> // For cerr +#include <memory> // For uninitialized_fill, uninitialized_copy +#include <utility> // for pair<> +#include <iterator> // for facts about iterator tags +#include "type_traits.h" // for true_type, integral_constant, etc. + +using std::pair; + +template <class Value, class Key, class HashFcn, + class ExtractKey, class EqualKey, class Alloc> +class dense_hashtable; + +template <class V, class K, class HF, class ExK, class EqK, class A> +struct dense_hashtable_iterator; + +template <class V, class K, class HF, class ExK, class EqK, class A> +struct dense_hashtable_const_iterator; + +// We're just an array, but we need to skip over empty and deleted elements +template <class V, class K, class HF, class ExK, class EqK, class A> +struct dense_hashtable_iterator { + public: + typedef dense_hashtable_iterator<V,K,HF,ExK,EqK,A> iterator; + typedef dense_hashtable_const_iterator<V,K,HF,ExK,EqK,A> const_iterator; + + typedef std::forward_iterator_tag iterator_category; + typedef V value_type; + typedef std::ptrdiff_t difference_type; + typedef size_t size_type; + typedef V& reference; // Value + typedef V* pointer; + + // "Real" constructor and default constructor + dense_hashtable_iterator(const dense_hashtable<V,K,HF,ExK,EqK,A> *h, + pointer it, pointer it_end, bool advance) + : ht(h), pos(it), end(it_end) { + if (advance) advance_past_empty_and_deleted(); + } + dense_hashtable_iterator() { } + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *pos; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic. The only hard part is making sure that + // we're not on an empty or marked-deleted array element + void advance_past_empty_and_deleted() { + while ( pos != end && (ht->test_empty(*this) || ht->test_deleted(*this)) ) + ++pos; + } + iterator& operator++() { + Assert(pos != end); ++pos; advance_past_empty_and_deleted(); return *this; + } + iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; } + + // Comparison. + bool operator==(const iterator& it) const { return pos == it.pos; } + bool operator!=(const iterator& it) const { return pos != it.pos; } + + + // The actual data + const dense_hashtable<V,K,HF,ExK,EqK,A> *ht; + pointer pos, end; +}; + + +// Now do it all again, but with const-ness! +template <class V, class K, class HF, class ExK, class EqK, class A> +struct dense_hashtable_const_iterator { + public: + typedef dense_hashtable_iterator<V,K,HF,ExK,EqK,A> iterator; + typedef dense_hashtable_const_iterator<V,K,HF,ExK,EqK,A> const_iterator; + + typedef std::forward_iterator_tag iterator_category; + typedef V value_type; + typedef std::ptrdiff_t difference_type; + typedef size_t size_type; + typedef const V& reference; // Value + typedef const V* pointer; + + // "Real" constructor and default constructor + dense_hashtable_const_iterator(const dense_hashtable<V,K,HF,ExK,EqK,A> *h, + pointer it, pointer it_end, bool advance) + : ht(h), pos(it), end(it_end) { + if (advance) advance_past_empty_and_deleted(); + } + dense_hashtable_const_iterator() { } + // This lets us convert regular iterators to const iterators + dense_hashtable_const_iterator(const iterator &it) + : ht(it.ht), pos(it.pos), end(it.end) { } + // The default destructor is fine; we don't define one + // The default operator= is fine; we don't define one + + // Happy dereferencer + reference operator*() const { return *pos; } + pointer operator->() const { return &(operator*()); } + + // Arithmetic. The only hard part is making sure that + // we're not on an empty or marked-deleted array element + void advance_past_empty_and_deleted() { + while ( pos != end && (ht->test_empty(*this) || ht->test_deleted(*this)) ) + ++pos; + } + const_iterator& operator++() { + Assert(pos != end); ++pos; advance_past_empty_and_deleted(); return *this; + } + const_iterator operator++(int) { const_iterator tmp(*this); ++*this; return tmp; } + + // Comparison. + bool operator==(const const_iterator& it) const { return pos == it.pos; } + bool operator!=(const const_iterator& it) const { return pos != it.pos; } + + + // The actual data + const dense_hashtable<V,K,HF,ExK,EqK,A> *ht; + pointer pos, end; +}; + +template <class Value, class Key, class HashFcn, + class ExtractKey, class EqualKey, class Alloc> +class dense_hashtable { + public: + typedef Key key_type; + typedef Value value_type; + typedef HashFcn hasher; + typedef EqualKey key_equal; + + typedef size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef dense_hashtable_iterator<Value, Key, HashFcn, + ExtractKey, EqualKey, Alloc> + iterator; + + typedef dense_hashtable_const_iterator<Value, Key, HashFcn, + ExtractKey, EqualKey, Alloc> + const_iterator; + + // How full we let the table get before we resize. Knuth says .8 is + // good -- higher causes us to probe too much, though saves memory + static const float HT_OCCUPANCY_FLT; // = 0.8; + + // How empty we let the table get before we resize lower. + // (0.0 means never resize lower.) + // It should be less than OCCUPANCY_FLT / 2 or we thrash resizing + static const float HT_EMPTY_FLT; // = 0.4 * HT_OCCUPANCY_FLT + + // Minimum size we're willing to let hashtables be. + // Must be a power of two, and at least 4. + // Note, however, that for a given hashtable, the initial size is a + // function of the first constructor arg, and may be >HT_MIN_BUCKETS. + static const size_t HT_MIN_BUCKETS = 32; + + + // ITERATOR FUNCTIONS + iterator begin() { return iterator(this, table, + table + num_buckets, true); } + iterator end() { return iterator(this, table + num_buckets, + table + num_buckets, true); } + const_iterator begin() const { return const_iterator(this, table, + table+num_buckets,true);} + const_iterator end() const { return const_iterator(this, table + num_buckets, + table+num_buckets,true);} + + // ACCESSOR FUNCTIONS for the things we templatize on, basically + hasher hash_funct() const { return hash; } + key_equal key_eq() const { return equals; } + + // Annoyingly, we can't copy values around, because they might have + // const components (they're probably pair<const X, Y>). We use + // explicit destructor invocation and placement new to get around + // this. Arg. + private: + void set_value(value_type* dst, const value_type& src) { + dst->~value_type(); + new(dst) value_type(src); + } + + void destroy_buckets(size_type first, size_type last) { + for ( ; first != last; ++first) + table[first].~value_type(); + } + + // DELETE HELPER FUNCTIONS + // This lets the user describe a key that will indicate deleted + // table entries. This key should be an "impossible" entry -- + // if you try to insert it for real, you won't be able to retrieve it! + // (NB: while you pass in an entire value, only the key part is looked + // at. This is just because I don't know how to assign just a key.) + private: + void squash_deleted() { // gets rid of any deleted entries we have + if ( num_deleted ) { // get rid of deleted before writing + dense_hashtable tmp(*this); // copying will get rid of deleted + swap(tmp); // now we are tmp + } + Assert(num_deleted == 0); + } + + public: + void set_deleted_key(const value_type &val) { + // the empty indicator (if specified) and the deleted indicator + // must be different + Assert(!use_empty || !equals(get_key(val), get_key(emptyval))); + // It's only safe to change what "deleted" means if we purge deleted guys + squash_deleted(); + use_deleted = true; + set_value(&delval, val); + } + void clear_deleted_key() { + squash_deleted(); + use_deleted = false; + } + + // These are public so the iterators can use them + // True if the item at position bucknum is "deleted" marker + bool test_deleted(size_type bucknum) const { + // The num_deleted test is crucial for read(): after read(), the ht values + // are garbage, and we don't want to think some of them are deleted. + return (use_deleted && num_deleted > 0 && + equals(get_key(delval), get_key(table[bucknum]))); + } + bool test_deleted(const iterator &it) const { + return (use_deleted && num_deleted > 0 && + equals(get_key(delval), get_key(*it))); + } + bool test_deleted(const const_iterator &it) const { + return (use_deleted && num_deleted > 0 && + equals(get_key(delval), get_key(*it))); + } + // Set it so test_deleted is true. true if object didn't used to be deleted + // See below (at erase()) to explain why we allow const_iterators + bool set_deleted(const_iterator &it) { + Assert(use_deleted); // bad if set_deleted_key() wasn't called + bool retval = !test_deleted(it); + // &* converts from iterator to value-type + set_value(const_cast<value_type*>(&(*it)), delval); + return retval; + } + // Set it so test_deleted is false. true if object used to be deleted + bool clear_deleted(const_iterator &it) { + Assert(use_deleted); // bad if set_deleted_key() wasn't called + // happens automatically when we assign something else in its place + return test_deleted(it); + } + + // EMPTY HELPER FUNCTIONS + // This lets the user describe a key that will indicate empty (unused) + // table entries. This key should be an "impossible" entry -- + // if you try to insert it for real, you won't be able to retrieve it! + // (NB: while you pass in an entire value, only the key part is looked + // at. This is just because I don't know how to assign just a key.) + public: + // These are public so the iterators can use them + // True if the item at position bucknum is "empty" marker + bool test_empty(size_type bucknum) const { + Assert(use_empty); // we always need to know what's empty! + return equals(get_key(emptyval), get_key(table[bucknum])); + } + bool test_empty(const iterator &it) const { + Assert(use_empty); // we always need to know what's empty! + return equals(get_key(emptyval), get_key(*it)); + } + bool test_empty(const const_iterator &it) const { + Assert(use_empty); // we always need to know what's empty! + return equals(get_key(emptyval), get_key(*it)); + } + + private: + // You can either set a range empty or an individual element + void set_empty(size_type bucknum) { + Assert(use_empty); + set_value(&table[bucknum], emptyval); + } + void fill_range_with_empty(value_type* table_start, value_type* table_end) { + // Like set_empty(range), but doesn't destroy previous contents + std::uninitialized_fill(table_start, table_end, emptyval); + } + void set_empty(size_type buckstart, size_type buckend) { + Assert(use_empty); + destroy_buckets(buckstart, buckend); + fill_range_with_empty(table + buckstart, table + buckend); + } + + public: + // TODO(csilvers): change all callers of this to pass in a key instead, + // and take a const key_type instead of const value_type. + void set_empty_key(const value_type &val) { + // Once you set the empty key, you can't change it + Assert(!use_empty); + // The deleted indicator (if specified) and the empty indicator + // must be different. + Assert(!use_deleted || !equals(get_key(val), get_key(delval))); + use_empty = true; + set_value(&emptyval, val); + + Assert(!table); // must set before first use + // num_buckets was set in constructor even though table was NULL + table = _Alval.allocate(num_buckets); + Assert(table); + fill_range_with_empty(table, table + num_buckets); + } + + // FUNCTIONS CONCERNING SIZE + public: + size_type size() const { return num_elements - num_deleted; } + // Buckets are always a power of 2 + size_type max_size() const { return (size_type(-1) >> 1U) + 1; } + bool empty() const { return size() == 0; } + size_type bucket_count() const { return num_buckets; } + size_type max_bucket_count() const { return max_size(); } + size_type nonempty_bucket_count() const { return num_elements; } + + private: + // Because of the above, size_type(-1) is never legal; use it for errors + static const size_type ILLEGAL_BUCKET = size_type(-1); + + private: + // This is the smallest size a hashtable can be without being too crowded + // If you like, you can give a min #buckets as well as a min #elts + size_type min_size(size_type num_elts, size_type min_buckets_wanted) { + size_type sz = HT_MIN_BUCKETS; // min buckets allowed + while ( sz < min_buckets_wanted || num_elts >= sz * HT_OCCUPANCY_FLT ) + sz *= 2; + return sz; + } + + // Used after a string of deletes + void maybe_shrink() { + Assert(num_elements >= num_deleted); + Assert((bucket_count() & (bucket_count()-1)) == 0); // is a power of two + Assert(bucket_count() >= HT_MIN_BUCKETS); + + if ( (num_elements-num_deleted) < shrink_threshold && + bucket_count() > HT_MIN_BUCKETS ) { + size_type sz = bucket_count() / 2; // find how much we should shrink + while ( sz > HT_MIN_BUCKETS && + (num_elements - num_deleted) < sz * HT_EMPTY_FLT ) + sz /= 2; // stay a power of 2 + dense_hashtable tmp(*this, sz); // Do the actual resizing + swap(tmp); // now we are tmp + } + consider_shrink = false; // because we just considered it + } + + // We'll let you resize a hashtable -- though this makes us copy all! + // When you resize, you say, "make it big enough for this many more elements" + void resize_delta(size_type delta, size_type min_buckets_wanted = 0) { + if ( consider_shrink ) // see if lots of deletes happened + maybe_shrink(); + if ( bucket_count() > min_buckets_wanted && + (num_elements + delta) <= enlarge_threshold ) + return; // we're ok as we are + + // Sometimes, we need to resize just to get rid of all the + // "deleted" buckets that are clogging up the hashtable. So when + // deciding whether to resize, count the deleted buckets (which + // are currently taking up room). But later, when we decide what + // size to resize to, *don't* count deleted buckets, since they + // get discarded during the resize. + const size_type needed_size = min_size(num_elements + delta, + min_buckets_wanted); + if ( needed_size > bucket_count() ) { // we don't have enough buckets + const size_type resize_to = min_size(num_elements - num_deleted + delta, + min_buckets_wanted); + dense_hashtable tmp(*this, resize_to); + swap(tmp); // now we are tmp + } + } + + // Increase number of buckets, assuming value_type has trivial copy + // constructor and destructor. (Really, we want it to have "trivial + // move", because that's what realloc does. But there's no way to + // capture that using type_traits, so we pretend that move(x, y) is + // equivalent to "x.~T(); new(x) T(y);" which is pretty much + // correct, if a bit conservative.) + void expand_array(size_t resize_to, dense_hash_map_traits::true_type) { + value_type* new_table = _Alval.allocate(resize_to); + Assert(new_table); + if(table) + { + std::uninitialized_copy(table, table + num_buckets, new_table); + _Alval.deallocate(table, num_buckets); + } + fill_range_with_empty(new_table + num_buckets, new_table + resize_to); + table = new_table; + Assert(table); + } + + // Increase number of buckets, without special assumptions about value_type. + // TODO(austern): make this exception safe. Handle exceptions from + // value_type's copy constructor. + void expand_array(size_t resize_to, dense_hash_map_traits::false_type) { + value_type* new_table = _Alval.allocate(resize_to); + Assert(new_table); + std::uninitialized_copy(table, table + std::min(num_buckets,resize_to), new_table); + fill_range_with_empty(new_table + num_buckets, new_table + resize_to); + destroy_buckets(0, num_buckets); + _Alval.deallocate(table, num_buckets); + table = new_table; + } + + // Used to actually do the rehashing when we grow/shrink a hashtable + void copy_from(const dense_hashtable &ht, size_type min_buckets_wanted = 0) { + clear(); // clear table, set num_deleted to 0 + + // If we need to change the size of our table, do it now + const size_type resize_to = min_size(ht.size(), min_buckets_wanted); + if ( resize_to > bucket_count() ) { // we don't have enough buckets + typedef dense_hash_map_traits::integral_constant<bool, + (dense_hash_map_traits::has_trivial_copy<value_type>::value && + dense_hash_map_traits::has_trivial_destructor<value_type>::value)> + realloc_ok; // we pretend mv(x,y) == "x.~T(); new(x) T(y)" + expand_array(resize_to, realloc_ok()); + num_buckets = resize_to; + reset_thresholds(); + } + + // We use a normal iterator to get non-deleted bcks from ht + // We could use insert() here, but since we know there are + // no duplicates and no deleted items, we can be more efficient + Assert((bucket_count() & (bucket_count()-1)) == 0); // a power of two + for ( const_iterator it = ht.begin(); it != ht.end(); ++it ) { + size_type num_probes = 0; // how many times we've probed + size_type bucknum; + const size_type bucket_count_minus_one = bucket_count() - 1; + for (bucknum = hash(get_key(*it)) & bucket_count_minus_one; + !test_empty(bucknum); // not empty + bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one) { + ++num_probes; + Assert(num_probes < bucket_count()); // or else the hashtable is full + } + set_value(&table[bucknum], *it); // copies the value to here + num_elements++; + } + } + + // Required by the spec for hashed associative container + public: + // Though the docs say this should be num_buckets, I think it's much + // more useful as req_elements. As a special feature, calling with + // req_elements==0 will cause us to shrink if we can, saving space. + void resize(size_type req_elements) { // resize to this or larger + if ( consider_shrink || req_elements == 0 ) + maybe_shrink(); + if ( req_elements > num_elements ) + return resize_delta(req_elements - num_elements, 0); + } + + + // CONSTRUCTORS -- as required by the specs, we take a size, + // but also let you specify a hashfunction, key comparator, + // and key extractor. We also define a copy constructor and =. + // DESTRUCTOR -- needs to free the table + explicit dense_hashtable(size_type n = 0, + const HashFcn& hf = HashFcn(), + const EqualKey& eql = EqualKey(), + const ExtractKey& ext = ExtractKey()) + : hash(hf), equals(eql), get_key(ext), num_deleted(0), + use_deleted(false), use_empty(false), + delval(), emptyval(), + table(NULL), num_buckets(min_size(0, n)), num_elements(0) { + // table is NULL until emptyval is set. However, we set num_buckets + // here so we know how much space to allocate once emptyval is set + reset_thresholds(); + } + + // As a convenience for resize(), we allow an optional second argument + // which lets you make this new hashtable a different size than ht + dense_hashtable(const dense_hashtable& ht, size_type min_buckets_wanted = 0) + : hash(ht.hash), equals(ht.equals), get_key(ht.get_key), num_deleted(0), + use_deleted(ht.use_deleted), use_empty(ht.use_empty), + delval(ht.delval), emptyval(ht.emptyval), + table(NULL), num_buckets(0), + num_elements(0) { + reset_thresholds(); + copy_from(ht, min_buckets_wanted); // copy_from() ignores deleted entries + } + + dense_hashtable& operator= (const dense_hashtable& ht) { + if (&ht == this) return *this; // don't copy onto ourselves + clear(); + hash = ht.hash; + equals = ht.equals; + get_key = ht.get_key; + use_deleted = ht.use_deleted; + use_empty = ht.use_empty; + set_value(&delval, ht.delval); + set_value(&emptyval, ht.emptyval); + copy_from(ht); // sets num_deleted to 0 too + return *this; + } + + ~dense_hashtable() { + if (table) { + destroy_buckets(0, num_buckets); + _Alval.deallocate(table, num_buckets); + } + } + + // Many STL algorithms use swap instead of copy constructors + void swap(dense_hashtable& ht) { + std::swap(hash, ht.hash); + std::swap(equals, ht.equals); + std::swap(get_key, ht.get_key); + std::swap(num_deleted, ht.num_deleted); + std::swap(use_deleted, ht.use_deleted); + std::swap(use_empty, ht.use_empty); + { value_type tmp; // for annoying reasons, swap() doesn't work + set_value(&tmp, delval); + set_value(&delval, ht.delval); + set_value(&ht.delval, tmp); + } + { value_type tmp; // for annoying reasons, swap() doesn't work + set_value(&tmp, emptyval); + set_value(&emptyval, ht.emptyval); + set_value(&ht.emptyval, tmp); + } + std::swap(table, ht.table); + std::swap(num_buckets, ht.num_buckets); + std::swap(num_elements, ht.num_elements); + reset_thresholds(); + ht.reset_thresholds(); + } + + // It's always nice to be able to clear a table without deallocating it + void clear() { + if (table) + destroy_buckets(0, num_buckets); + + size_type old_bucket_count = num_buckets; + num_buckets = min_size(0,0); // our new size + reset_thresholds(); + value_type* new_table = _Alval.allocate(num_buckets); + Assert(new_table); + if(table) + _Alval.deallocate(table, old_bucket_count); + table = new_table; + Assert(table); + fill_range_with_empty(table, table + num_buckets); + num_elements = 0; + num_deleted = 0; + } + + // Clear the table without resizing it. + // Mimicks the stl_hashtable's behaviour when clear()-ing in that it + // does not modify the bucket count + void clear_no_resize() { + if (table) { + set_empty(0, num_buckets); + } + // don't consider to shrink before another erase() + reset_thresholds(); + num_elements = 0; + num_deleted = 0; + } + + // LOOKUP ROUTINES + private: + // Returns a pair of positions: 1st where the object is, 2nd where + // it would go if you wanted to insert it. 1st is ILLEGAL_BUCKET + // if object is not found; 2nd is ILLEGAL_BUCKET if it is. + // Note: because of deletions where-to-insert is not trivial: it's the + // first deleted bucket we see, as long as we don't find the key later + pair<size_type, size_type> find_position(const key_type &key) const { + size_type num_probes = 0; // how many times we've probed + const size_type bucket_count_minus_one = bucket_count() - 1; + size_type bucknum = hash(key) & bucket_count_minus_one; + size_type insert_pos = ILLEGAL_BUCKET; // where we would insert + while ( 1 ) { // probe until something happens + if ( test_empty(bucknum) ) { // bucket is empty + if ( insert_pos == ILLEGAL_BUCKET ) // found no prior place to insert + return pair<size_type,size_type>(ILLEGAL_BUCKET, bucknum); + else + return pair<size_type,size_type>(ILLEGAL_BUCKET, insert_pos); + + } else if ( test_deleted(bucknum) ) {// keep searching, but mark to insert + if ( insert_pos == ILLEGAL_BUCKET ) + insert_pos = bucknum; + + } else if ( equals(key, get_key(table[bucknum])) ) { + return pair<size_type,size_type>(bucknum, ILLEGAL_BUCKET); + } + ++num_probes; // we're doing another probe + bucknum = (bucknum + JUMP_(key, num_probes)) & bucket_count_minus_one; + Assert(num_probes < bucket_count()); // don't probe too many times! + } + } + + public: + iterator find(const key_type& key) { + if ( size() == 0 ) return end(); + pair<size_type, size_type> pos = find_position(key); + if ( pos.first == ILLEGAL_BUCKET ) // alas, not there + return end(); + else + return iterator(this, table + pos.first, table + num_buckets, false); + } + + const_iterator find(const key_type& key) const { + if ( size() == 0 ) return end(); + pair<size_type, size_type> pos = find_position(key); + if ( pos.first == ILLEGAL_BUCKET ) // alas, not there + return end(); + else + return const_iterator(this, table + pos.first, table+num_buckets, false); + } + + // Counts how many elements have key key. For maps, it's either 0 or 1. + size_type count(const key_type &key) const { + pair<size_type, size_type> pos = find_position(key); + return pos.first == ILLEGAL_BUCKET ? 0 : 1; + } + + // Likewise, equal_range doesn't really make sense for us. Oh well. + pair<iterator,iterator> equal_range(const key_type& key) { + const iterator pos = find(key); // either an iterator or end + return pair<iterator,iterator>(pos, pos); + } + pair<const_iterator,const_iterator> equal_range(const key_type& key) const { + const const_iterator pos = find(key); // either an iterator or end + return pair<iterator,iterator>(pos, pos); + } + + + // INSERTION ROUTINES + private: + // If you know *this is big enough to hold obj, use this routine + pair<iterator, bool> insert_noresize(const value_type& obj) { + const pair<size_type,size_type> pos = find_position(get_key(obj)); + if ( pos.first != ILLEGAL_BUCKET) { // object was already there + return pair<iterator,bool>(iterator(this, table + pos.first, + table + num_buckets, false), + false); // false: we didn't insert + } else { // pos.second says where to put it + if ( test_deleted(pos.second) ) { // just replace if it's been del. + const_iterator delpos(this, table + pos.second, // shrug: + table + num_buckets, false);// shouldn't need const + clear_deleted(delpos); + Assert( num_deleted > 0); + --num_deleted; // used to be, now it isn't + } else { + ++num_elements; // replacing an empty bucket + } + set_value(&table[pos.second], obj); + return pair<iterator,bool>(iterator(this, table + pos.second, + table + num_buckets, false), + true); // true: we did insert + } + } + + public: + // This is the normal insert routine, used by the outside world + pair<iterator, bool> insert(const value_type& obj) { + resize_delta(1); // adding an object, grow if need be + return insert_noresize(obj); + } + + // When inserting a lot at a time, we specialize on the type of iterator + template <class InputIterator> + void insert(InputIterator f, InputIterator l) { + // specializes on iterator type + insert(f, l, typename std::iterator_traits<InputIterator>::iterator_category()); + } + + // Iterator supports operator-, resize before inserting + template <class ForwardIterator> + void insert(ForwardIterator f, ForwardIterator l, + std::forward_iterator_tag) { + size_type n = std::distance(f, l); // TODO(csilvers): standard? + resize_delta(n); + for ( ; n > 0; --n, ++f) + insert_noresize(*f); + } + + // Arbitrary iterator, can't tell how much to resize + template <class InputIterator> + void insert(InputIterator f, InputIterator l, + std::input_iterator_tag) { + for ( ; f != l; ++f) + insert(*f); + } + + + // DELETION ROUTINES + size_type erase(const key_type& key) { + const_iterator pos = find(key); // shrug: shouldn't need to be const + if ( pos != end() ) { + Assert(!test_deleted(pos)); // or find() shouldn't have returned it + set_deleted(pos); + ++num_deleted; + consider_shrink = true; // will think about shrink after next insert + return 1; // because we deleted one thing + } else { + return 0; // because we deleted nothing + } + } + + // This is really evil: really it should be iterator, not const_iterator. + // But...the only reason keys are const is to allow lookup. + // Since that's a moot issue for deleted keys, we allow const_iterators + void erase(const_iterator pos) { + if ( pos == end() ) return; // sanity check + if ( set_deleted(pos) ) { // true if object has been newly deleted + ++num_deleted; + consider_shrink = true; // will think about shrink after next insert + } + } + + void erase(const_iterator f, const_iterator l) { + for ( ; f != l; ++f) { + if ( set_deleted(f) ) // should always be true + ++num_deleted; + } + consider_shrink = true; // will think about shrink after next insert + } + + + // COMPARISON + bool operator==(const dense_hashtable& ht) const { + if (size() != ht.size()) { + return false; + } else if (this == &ht) { + return true; + } else { + // Iterate through the elements in "this" and see if the + // corresponding element is in ht + for ( const_iterator it = begin(); it != end(); ++it ) { + const_iterator it2 = ht.find(get_key(*it)); + if ((it2 == ht.end()) || (*it != *it2)) { + return false; + } + } + return true; + } + } + bool operator!=(const dense_hashtable& ht) const { + return !(*this == ht); + } + + + private: + // The actual data + hasher hash; // required by hashed_associative_container + key_equal equals; + ExtractKey get_key; + size_type num_deleted; // how many occupied buckets are marked deleted + bool use_deleted; // false until delval has been set + bool use_empty; // you must do this before you start + value_type delval; // which key marks deleted entries + value_type emptyval; // which key marks unused entries + value_type *table; + size_type num_buckets; + size_type num_elements; + size_type shrink_threshold; // num_buckets * HT_EMPTY_FLT + size_type enlarge_threshold; // num_buckets * HT_OCCUPANCY_FLT + bool consider_shrink; // true if we should try to shrink before next insert + + Alloc _Alval; // allocator object for value_type + + void reset_thresholds() { + enlarge_threshold = static_cast<size_type>(num_buckets*HT_OCCUPANCY_FLT); + shrink_threshold = static_cast<size_type>(num_buckets*HT_EMPTY_FLT); + consider_shrink = false; // whatever caused us to reset already considered + } +}; + +// We need a global swap as well +template <class V, class K, class HF, class ExK, class EqK, class A> +inline void swap(dense_hashtable<V,K,HF,ExK,EqK,A> &x, + dense_hashtable<V,K,HF,ExK,EqK,A> &y) { + x.swap(y); +} + +#undef JUMP_ + +template <class V, class K, class HF, class ExK, class EqK, class A> +const typename dense_hashtable<V,K,HF,ExK,EqK,A>::size_type + dense_hashtable<V,K,HF,ExK,EqK,A>::ILLEGAL_BUCKET; + +// How full we let the table get before we resize. Knuth says .8 is +// good -- higher causes us to probe too much, though saves memory +template <class V, class K, class HF, class ExK, class EqK, class A> +const float dense_hashtable<V,K,HF,ExK,EqK,A>::HT_OCCUPANCY_FLT = 0.5f; + +// How empty we let the table get before we resize lower. +// It should be less than OCCUPANCY_FLT / 2 or we thrash resizing +template <class V, class K, class HF, class ExK, class EqK, class A> +const float dense_hashtable<V,K,HF,ExK,EqK,A>::HT_EMPTY_FLT = 0.4f * +dense_hashtable<V,K,HF,ExK,EqK,A>::HT_OCCUPANCY_FLT; + +#endif /* _DENSEHASHTABLE_H_ */ diff --git a/Runtime/Utilities/dynamic_array.h b/Runtime/Utilities/dynamic_array.h new file mode 100644 index 0000000..90573d9 --- /dev/null +++ b/Runtime/Utilities/dynamic_array.h @@ -0,0 +1,340 @@ +#pragma once + +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Utilities/StaticAssert.h" + +#include <memory> // std::uninitialized_fill + +// dynamic_array - simplified version of std::vector<T> +// +// features: +// . always uses memcpy for copying elements. Your data structures must be simple and can't have internal pointers / rely on copy constructor. +// . EASTL like push_back(void) implementation +// Existing std STL implementations implement insertion operations by copying from an element. +// For example, resize(size() + 1) creates a throw-away temporary object. +// There is no way in existing std STL implementations to add an element to a container without implicitly or +// explicitly providing one to copy from (aside from some existing POD optimizations). +// For expensive-to-construct objects this creates a potentially serious performance problem. +// . grows X2 on reallocation +// . small code footprint +// . clear actually deallocates memory +// . resize does NOT initialize members! +// +// Changelog: +// Added pop_back() +// Added assign() +// Added clear() - frees the data, use resize(0) to clear w/o freeing +// zero allocation for empty array +// +// +template<typename T> +struct AlignOfType +{ + enum { align = ALIGN_OF(T) }; +}; + + +template <typename T, size_t align = AlignOfType<T>::align, MemLabelIdentifier defaultLabel = kMemDynamicArrayId> +struct dynamic_array +{ +public: + typedef T* iterator; + typedef const T* const_iterator; + typedef T value_type; + typedef size_t size_type; + typedef size_t difference_type; + typedef T& reference; + typedef const T& const_reference; + +public: + + dynamic_array() : m_data(NULL), m_label(defaultLabel, NULL), m_size(0), m_capacity(0) + { + m_label = MemLabelId(defaultLabel, GET_CURRENT_ALLOC_ROOT_HEADER()); + } + + dynamic_array(MemLabelRef label) : m_data(NULL), m_label(label), m_size(0), m_capacity(0) + { + } + + explicit dynamic_array (size_t size, MemLabelRef label) + : m_label(label), m_size(size), m_capacity (size) + { + m_data = allocate (size); + } + + dynamic_array (size_t size, T const& init_value, MemLabelRef label) + : m_label(label), m_size (size), m_capacity (size) + { + m_data = allocate (size); + std::uninitialized_fill (m_data, m_data + size, init_value); + } + + ~dynamic_array() + { + if (owns_data()) + m_data = deallocate(m_data); + } + + dynamic_array(const dynamic_array& other) : m_capacity(0), m_size(0), m_label(other.m_label) + { + //m_label.SetRootHeader(GET_CURRENT_ALLOC_ROOT_HEADER()); + m_data = NULL; + assign(other.begin(), other.end()); + } + + dynamic_array& operator=(const dynamic_array& other) + { + // should not allocate memory unless we have to + assign(other.begin(), other.end()); + return *this; + } + + void clear() + { + if (owns_data()) + m_data = deallocate(m_data); + m_size = 0; + m_capacity = 0; + } + + void assign(const_iterator begin, const_iterator end) + { + Assert(begin<=end); + + resize_uninitialized(end-begin); + memcpy(m_data, begin, m_size * sizeof(T)); + } + + void erase(iterator input_begin, iterator input_end) + { + Assert(input_begin <= input_end); + Assert(input_begin >= begin()); + Assert(input_end <= end()); + + size_t leftOverSize = end() - input_end; + memmove(input_begin, input_end, leftOverSize * sizeof(T)); + m_size -= input_end - input_begin; + } + + iterator erase(iterator position) + { + Assert(position >= begin()); + Assert(position < end()); + + size_t leftOverSize = end() - position - 1; + memmove(position, position+1, leftOverSize * sizeof(T)); + m_size -= 1; + + return position; + } + + iterator insert(iterator insert_before, const_iterator input_begin, const_iterator input_end) + { + Assert(input_begin <= input_end); + Assert(insert_before >= begin()); + Assert(insert_before <= end()); + + // resize (make sure that insertBefore does not get invalid in the meantime because of a reallocation) + size_t insert_before_index = insert_before - begin(); + size_t elements_to_be_moved = size() - insert_before_index; + resize_uninitialized((input_end - input_begin) + size()); + insert_before = begin() + insert_before_index; + + size_t insertsize = input_end - input_begin; + // move to the end of where the inserted data will be + memmove(insert_before + insertsize, insert_before, elements_to_be_moved * sizeof(T)); + // inject input data in the hole we just created + memcpy(insert_before, input_begin, insertsize * sizeof(T)); + + return insert_before; + } + + iterator insert(iterator insertBefore, const T& t) { return insert(insertBefore, &t, &t + 1); } + + void swap(dynamic_array& other) throw() + { + if (m_data) UNITY_TRANSFER_OWNERSHIP_TO_HEADER(m_data, m_label, other.m_label.GetRootHeader()); + if (other.m_data) UNITY_TRANSFER_OWNERSHIP_TO_HEADER(other.m_data, other.m_label, m_label.GetRootHeader()); + std::swap(m_data, other.m_data); + std::swap(m_size, other.m_size); + std::swap(m_capacity, other.m_capacity); + std::swap(m_label, other.m_label); + } + + T& push_back() + { + if (++m_size > capacity()) + reserve(std::max<size_t>(capacity()*2, 1)); + return back(); + } + + void push_back(const T& t) + { + push_back() = t; + } + + void pop_back() + { + Assert(m_size >= 1); + m_size--; + } + + void resize_uninitialized(size_t size, bool double_on_resize = false) + { + m_size = size; + if (m_size <= capacity()) + return; + + if(double_on_resize && size < capacity()*2) + size = capacity()*2; + reserve(size); + } + + void resize_initialized(size_t size, const T& t = T(), bool double_on_resize = false) + { + if (size > capacity()) + { + size_t requested_size = size; + if(double_on_resize && size < capacity()*2) + requested_size = capacity()*2; + reserve(requested_size); + } + + if (size > m_size) + std::uninitialized_fill (m_data + m_size, m_data + size, t); + m_size = size; + } + + void reserve(size_t inCapacity) + { + if (capacity() >= inCapacity) + return; + + if (owns_data()) + { + m_capacity = inCapacity; + m_data = reallocate(m_data, inCapacity); + } + else + { + T* newData = allocate(inCapacity); + memcpy(newData, m_data, m_size * sizeof(T)); + + // Invalidate old non-owned data, since using the data from two places is most likely a really really bad idea. + #if DEBUGMODE + memset(m_data, 0xCD, capacity() * sizeof(T)); + #endif + + m_capacity = inCapacity; // and clear reference bit + m_data = newData; + } + } + + void assign_external (T* begin, T* end) + { + if (owns_data()) + m_data = deallocate(m_data); + m_size = m_capacity = reinterpret_cast<value_type*> (end) - reinterpret_cast<value_type*> (begin); + Assert(m_size < k_reference_bit); + m_capacity |= k_reference_bit; + m_data = begin; + } + + void set_owns_data (bool ownsData) + { + if (ownsData) + m_capacity &= ~k_reference_bit; + else + m_capacity |= k_reference_bit; + } + + void shrink_to_fit() + { + if (owns_data()) + { + m_capacity = m_size; + m_data = reallocate(m_data, m_size); + } + } + + const T& back() const { Assert (m_size != 0); return m_data[m_size - 1]; } + const T& front() const { Assert (m_size != 0); return m_data[0]; } + + T& back() { Assert (m_size != 0); return m_data[m_size - 1]; } + T& front() { Assert (m_size != 0); return m_data[0]; } + + T* data () { return m_data; } + T const* data () const { return m_data; } + + bool empty () const { return m_size == 0; } + size_t size() const { return m_size; } + size_t capacity() const { return m_capacity & ~k_reference_bit; } + + T const& operator[] (size_t index) const { DebugAssert(index < m_size); return m_data[index]; } + T& operator[] (size_t index) { DebugAssert(index < m_size); return m_data[index]; } + + T const* begin() const { return m_data; } + T* begin() { return m_data; } + + T const* end() const { return m_data + m_size; } + T* end() { return m_data + m_size; } + + bool owns_data() { return (m_capacity & k_reference_bit) == 0; } + + bool equals(const dynamic_array& other) + { + if(m_size != other.m_size) + return false; + + for( int i = 0; i < m_size; i++) + { + if (m_data[i] != other.m_data[i]) + return false; + } + + return true; + } + + void set_memory_label (MemLabelRef label) + { + Assert(m_data == NULL); + m_label = label; + } + +private: + + static const size_t k_reference_bit = (size_t)1 << (sizeof (size_t) * 8 - 1); + + T* allocate (size_t size) + { + // If you are getting this error then you are trying to allocate memory for an incomplete type + CompileTimeAssert(sizeof(T) != 0, "incomplete type"); + CompileTimeAssert(align != 0, "incomplete type"); + + return static_cast<T*> (UNITY_MALLOC_ALIGNED (m_label, size * sizeof(T), align)); + } + + T* deallocate (T* data) + { + Assert(owns_data()); + UNITY_FREE (m_label, data); + return NULL; + } + + T* reallocate (T* data, size_t size) + { + // If you are getting this error then you are trying to allocate memory for an incomplete type + CompileTimeAssert(sizeof(T) != 0, "incomplete type"); + CompileTimeAssert(align != 0, "incomplete type"); + + Assert(owns_data()); + int alignof = static_cast<int>(align); + return static_cast<T*> (UNITY_REALLOC_ALIGNED(m_label, data, size * sizeof(T), alignof)); + } + + T* m_data; + MemLabelId m_label; + size_t m_size; + size_t m_capacity; +}; diff --git a/Runtime/Utilities/dynamic_array_tests.cpp b/Runtime/Utilities/dynamic_array_tests.cpp new file mode 100644 index 0000000..8169fd9 --- /dev/null +++ b/Runtime/Utilities/dynamic_array_tests.cpp @@ -0,0 +1,254 @@ +#include "UnityPrefix.h" + +#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Utilities/ArrayUtility.h" + + +TEST (DynamicArray) +{ + // no allocation for empty array + dynamic_array<int> array; + CHECK_EQUAL (0, array.capacity ()); + + // push_back allocates + int j = 1; + array.push_back (j); + CHECK_EQUAL (1, array.size ()); + CHECK (array.capacity () > 0); + + // push_back(void) + int& i = array.push_back (); + i = 666; + CHECK_EQUAL(666, array.back ()); + + // clear frees memory? + array.clear (); + CHECK_EQUAL (0, array.size ()); + CHECK_EQUAL (0, array.capacity ()); + + // 3 item list + j = 6; + array.push_back (j); + j = 7; + array.push_back (j); + j = 8; + array.push_back (j); + + CHECK_EQUAL (3, array.size ()); + + // swapping + dynamic_array<int> ().swap (array); + CHECK_EQUAL (0, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // reserve + array.reserve (1024); + CHECK_EQUAL (1024, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // copy assignment + dynamic_array<int> array1; + j = 888; + array1.push_back (j); + + array = array1; + + CHECK_EQUAL (1, array.size ()); + CHECK_EQUAL (888, array.back ()); + CHECK_EQUAL (1, array1.size ()); + CHECK_EQUAL (888, array1.back ()); +} + +TEST (DynamicArrayMisc) +{ + // no allocation for empty array + dynamic_array<int> array; + CHECK_EQUAL (0, array.capacity ()); + CHECK (array.owns_data ()); + CHECK (array.begin () == array.end ()); + CHECK (array.empty ()); + + // push_back allocates + int j = 1; + array.push_back (j); + CHECK_EQUAL (1, array.size ()); + CHECK (array.capacity () > 0); + + // push_back(void) + int& i = array.push_back (); + i = 666; + CHECK_EQUAL(666, array.back ()); + + // clear frees memory? + array.clear (); + CHECK_EQUAL (0, array.size ()); + CHECK_EQUAL (0, array.capacity ()); + + // 3 item list + array.push_back (6); + array.push_back (7); + array.push_back (8); + + CHECK_EQUAL (3, array.size ()); + + // swapping + dynamic_array<int> ().swap (array); + CHECK_EQUAL (0, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // reserve + array.reserve (1024); + CHECK_EQUAL (1024, array.capacity ()); + CHECK_EQUAL (0, array.size ()); + + // copy assignment + dynamic_array<int> array1; + j = 888; + array1.push_back (j); + + array = array1; + + CHECK_EQUAL (1, array.size ()); + CHECK_EQUAL (888, array.back ()); + CHECK_EQUAL (1, array1.size ()); + CHECK_EQUAL (888, array1.back ()); +} + + +TEST (DynamicArrayErase) +{ + dynamic_array<int> arr; + arr.push_back(1); + arr.push_back(2); + arr.push_back(3); + arr.push_back(4); + arr.push_back(5); + dynamic_array<int>::iterator it; + + // erase first elem + it = arr.erase(arr.begin()); + CHECK_EQUAL (2, *it); + CHECK_EQUAL (4, arr.size()); + CHECK_EQUAL (2, arr[0]); + CHECK_EQUAL (3, arr[1]); + CHECK_EQUAL (4, arr[2]); + CHECK_EQUAL (5, arr[3]); + + // erase 2nd to last elem + it = arr.erase(arr.end()-2); + CHECK_EQUAL (5, *it); + CHECK_EQUAL (3, arr.size()); + CHECK_EQUAL (2, arr[0]); + CHECK_EQUAL (3, arr[1]); + CHECK_EQUAL (5, arr[2]); + + // erase last elem + it = arr.erase(arr.end()-1); + CHECK_EQUAL (arr.end(), it); + CHECK_EQUAL (2, arr.size()); + CHECK_EQUAL (2, arr[0]); + CHECK_EQUAL (3, arr[1]); +} + + +TEST (DynamicArrayEraseRange) +{ + dynamic_array<int> vs; + vs.resize_uninitialized(5); + + vs[0] = 0; + vs[1] = 1; + vs[2] = 2; + vs[3] = 3; + vs[4] = 4; + + vs.erase(vs.begin() + 1, vs.begin() + 4); + CHECK_EQUAL (2, vs.size()); + CHECK_EQUAL (5, vs.capacity()); + CHECK_EQUAL (0, vs[0]); + CHECK_EQUAL (4, vs[1]); +} + +static void VerifyConsecutiveIntArray (dynamic_array<int>& vs, int size, int capacity) +{ + CHECK_EQUAL (capacity, vs.capacity()); + CHECK_EQUAL (size, vs.size()); + for (int i=0;i<vs.size();i++) + CHECK_EQUAL (i, vs[i]); +} + +TEST (DynamicArrayInsertOnEmpty) +{ + dynamic_array<int> vs; + int vals[] = { 0, 1 }; + + vs.insert(vs.begin(), vals, vals + ARRAY_SIZE(vals)); + + VerifyConsecutiveIntArray(vs, 2, 2); +} + + +TEST (DynamicArrayInsert) +{ + dynamic_array<int> vs; + vs.resize_uninitialized(5); + + vs[0] = 0; + vs[1] = 1; + vs[2] = 4; + vs[3] = 5; + vs[4] = 6; + + int vals[] = { 2, 3 }; + + // inser two values + vs.insert(vs.begin() + 2, vals, vals + ARRAY_SIZE(vals)); + VerifyConsecutiveIntArray(vs, 7, 7); + + // empty insert + vs.insert(vs.begin() + 2, vals, vals); + + VerifyConsecutiveIntArray(vs, 7, 7); +} + +TEST (DynamicArrayResize) +{ + dynamic_array<int> vs; + vs.resize_initialized(3, 2); + CHECK_EQUAL (3, vs.capacity()); + CHECK_EQUAL (3, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); + CHECK_EQUAL (2, vs[2]); + + vs.resize_initialized(6, 3); + CHECK_EQUAL (6, vs.capacity()); + CHECK_EQUAL (6, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); + CHECK_EQUAL (2, vs[2]); + CHECK_EQUAL (3, vs[3]); + CHECK_EQUAL (3, vs[4]); + CHECK_EQUAL (3, vs[5]); + + vs.resize_initialized(5, 3); + CHECK_EQUAL (6, vs.capacity()); + CHECK_EQUAL (5, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); + CHECK_EQUAL (2, vs[2]); + CHECK_EQUAL (3, vs[3]); + CHECK_EQUAL (3, vs[4]); + + vs.resize_initialized(2, 3); + CHECK_EQUAL (6, vs.capacity()); + CHECK_EQUAL (2, vs.size()); + CHECK_EQUAL (2, vs[0]); + CHECK_EQUAL (2, vs[1]); +} + + +#endif // #if ENABLE_UNIT_TESTS diff --git a/Runtime/Utilities/dynamic_bitset.h b/Runtime/Utilities/dynamic_bitset.h new file mode 100644 index 0000000..7dadb2c --- /dev/null +++ b/Runtime/Utilities/dynamic_bitset.h @@ -0,0 +1,1144 @@ +#ifndef DYNAMIC_BITSET_H +#define DYNAMIC_BITSET_H +// (C) Copyright Chuck Allison and Jeremy Siek 2001, 2002. +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all +// copies. This software is provided "as is" without express or +// implied warranty, and with no claim as to its suitability for any +// purpose. + +// With optimizations, bug fixes, and improvements by Gennaro Prota. + +// See http://www.boost.org/libs/dynamic_bitset for documentation. + +// ------------------------------------- +// CHANGE LOG: +// +// - corrected workaround for Dinkum lib's allocate() [GP] +// - changed macro test for old iostreams [GP] +// - removed #include <vector> for now. [JGS] +// - Added __GNUC__ to compilers that cannot handle the constructor from basic_string. [JGS] +// - corrected to_block_range [GP] +// - corrected from_block_range [GP] +// - Removed __GNUC__ from compilers that cannot handle the constructor +// from basic_string and added the workaround suggested by GP. [JGS] +// - Removed __BORLANDC__ from the #if around the basic_string +// constructor. Luckily the fix by GP for g++ also fixes Borland. [JGS] + +#include <cassert> +#include <string> +#include <cstring> // for memset, memcpy, memcmp, etc. +#include <algorithm> // for std::swap, std::min, std::copy, std::fill +#include <memory> // for std::swap, std::min, std::copy, std::fill +#include <stdlib.h> +#include "LogAssert.h" + +namespace std +{ + typedef ::size_t size_t; +} +// (C) Copyright Chuck Allison and Jeremy Siek 2001, 2002. +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all +// copies. This software is provided "as is" without express or +// implied warranty, and with no claim as to its suitability for any +// purpose. + +// With optimizations by Gennaro Prota. + + class dynamic_bitset_base + { + typedef std::size_t size_type; + public: +#if defined(LINUX) && (defined (__LP64__) || defined(_AMD64_)) + typedef unsigned int Block; +#else + typedef unsigned long Block; +#endif + enum { bits_per_block = 8 * sizeof(Block) }; + + dynamic_bitset_base() + : m_bits(0), m_num_bits(0), m_num_blocks(0) { } + + dynamic_bitset_base(size_type num_bits) : + m_num_bits(num_bits), + m_num_blocks(calc_num_blocks(num_bits)) + { + if (m_num_blocks != 0) + { + m_bits = new Block[m_num_blocks]; + memset(m_bits, 0, m_num_blocks * sizeof(Block)); // G.P.S. ask to Jeremy + } + else + m_bits = 0; + } + ~dynamic_bitset_base() { + delete []m_bits;; + } + + Block* m_bits; + size_type m_num_bits; + size_type m_num_blocks; + + static size_type word(size_type bit) { return bit / bits_per_block; } // [gps] + static size_type offset(size_type bit){ return bit % bits_per_block; } // [gps] + static Block mask1(size_type bit) { return Block(1) << offset(bit); } + static Block mask0(size_type bit) { return ~(Block(1) << offset(bit)); } + static size_type calc_num_blocks(size_type num_bits) + { return (num_bits + bits_per_block - 1) / bits_per_block; } + }; + + + // ------- count table implementation -------------- + + typedef unsigned char byte_t; + + template <bool bogus = true> + struct bitcount { + typedef byte_t element_type; + static const byte_t table[]; + + }; + //typedef count<true> table_t; + + + // the table: wrapped in a class template, so + // that it is only instantiated if/when needed + // + template <bool bogus> + const byte_t bitcount<bogus>::table[] = + { + // Automatically generated by GPTableGen.exe v.1.0 + // + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 + }; + + + // ------------------------------------------------------- + template <typename BlockInputIterator> + std::size_t initial_num_blocks(BlockInputIterator first, + BlockInputIterator last) + { + std::size_t n = 0; + while (first != last) + ++first, ++n; + return n; + } + +class dynamic_bitset : public dynamic_bitset_base +{ + public: + + typedef Block block_type; + typedef std::size_t size_type; + enum { bits_per_block = 8 * sizeof(Block) }; + + // reference to a bit + class reference + { + friend class dynamic_bitset; + dynamic_bitset* bs; + size_type bit; + reference(); // intentionally not implemented + reference(dynamic_bitset& bs_, size_type bit_) : bs(&bs_), bit(bit_){ } + public: + reference& operator=(bool value) // for b[i] = x + { + if (value) + bs->set(bit); + else + bs->reset(bit); + return *this; + } + reference& operator|=(bool value) // for b[i] |= x + { + if (value) + bs->set(bit); + return *this; + } + reference& operator&=(bool value) // for b[i] &= x + { + if (! (value && bs->test(bit))) + bs->reset(bit); + return *this; + } + reference& operator^=(bool value) // for b[i] ^= x + { + bs->set(bit, bs->test(bit) ^ value); + return *this; + } + reference& operator-=(bool value) // for b[i] -= x + { + if (!value) + bs->reset(bit); + return *this; + } + reference& operator=(const reference& j) // for b[i] = b[j] + { + if (j.bs->test(j.bit)) + bs->set(bit); + else + bs->reset(bit); + return *this; + } + reference& operator|=(const reference& j) // for b[i] |= b[j] + { + if (j.bs->test(j.bit)) + bs->set(bit); + return *this; + } + reference& operator&=(const reference& j) // for b[i] &= b[j] + { + if (! (j.bs->test(j.bit) && bs->test(bit))) + bs->reset(bit); + return *this; + } + reference& operator^=(const reference& j) // for b[i] ^= b[j] + { + bs->set(bit, bs->test(bit) ^ j.bs->test(j.bit)); + return *this; + } + reference& operator-=(const reference& j) // for b[i] -= b[j] + { + if (!j.bs->test(j.bit)) + bs->reset(bit); + return *this; + } + bool operator~() const // flips the bit + { + return ! bs->test(bit); + } + operator bool() const // for x = b[i] + { + return bs->test(bit); + } + reference& flip() // for b[i].flip(); + { + bs->flip(bit); + return *this; + } + }; + typedef bool const_reference; + + dynamic_bitset (); + explicit + dynamic_bitset(size_type num_bits, unsigned long value = 0); + + // The parenthesis around std::basic_string<CharT, Traits, Alloc>::npos + // in the code below are to avoid a g++ 3.2 bug and a Borland bug. -JGS + template <typename String> + explicit + dynamic_bitset(const String& s, + typename String::size_type pos = 0, + typename String::size_type n + = (String::npos)) + : dynamic_bitset_base + (std::min(n, s.size() - pos)) + { + // Locate sub string + AssertIf (pos > s.length()); + from_string(s, pos, std::min(n, s.size() - pos)); + } + + // The first bit in *first is the least significant bit, and the + // last bit in the block just before *last is the most significant bit. + template <typename BlockInputIterator> + dynamic_bitset(BlockInputIterator first, BlockInputIterator last) + : dynamic_bitset_base + (initial_num_blocks(first, last) + * bits_per_block) + { + if (first != last) { + if (this->m_num_bits == 0) { // dealing with input iterators + this->append(first, last); + } else { + // dealing with forward iterators, memory has been allocated + for (std::size_t i = 0; first != last; ++first, ++i) + set_block_(i, *first); + } + } + } + + + // copy constructor + dynamic_bitset(const dynamic_bitset& b); + + void swap(dynamic_bitset& b); + + dynamic_bitset& operator=(const dynamic_bitset& b); + + // size changing operations + void resize(size_type num_bits, bool value = false); + void clear(); + void push_back(bool bit); + void append(Block block); + + // This is declared inside the class to avoid compiler bugs. + template <typename BlockInputIterator> + void append(BlockInputIterator first, BlockInputIterator last) + { + if (first != last) { + std::size_t nblocks = initial_num_blocks(first, last); + if (nblocks == 0) { // dealing with input iterators + for (; first != last; ++first) + append(*first); + } else { // dealing with forward iterators + if (size() % bits_per_block == 0) { + std::size_t old_nblocks = this->m_num_blocks; + resize(size() + nblocks * bits_per_block); + for (std::size_t i = old_nblocks; first != last; ++first) + set_block_(i++, *first); + } else { + // probably should optimize this, + // but I'm sick of bit twiddling + for (; first != last; ++first) + append(*first); + } + } + } + } + + + // bitset operations + dynamic_bitset& operator&=(const dynamic_bitset& b); + dynamic_bitset& operator|=(const dynamic_bitset& b); + dynamic_bitset& operator^=(const dynamic_bitset& b); + dynamic_bitset& operator-=(const dynamic_bitset& b); + dynamic_bitset& operator<<=(size_type n); + dynamic_bitset& operator>>=(size_type n); + dynamic_bitset operator<<(size_type n) const; + dynamic_bitset operator>>(size_type n) const; + + // basic bit operations + dynamic_bitset& set(size_type n, bool val = true); + dynamic_bitset& set(); + dynamic_bitset& reset(size_type n); + dynamic_bitset& reset(); + dynamic_bitset& flip(size_type n); + dynamic_bitset& flip(); + bool test(size_type n) const; + bool any() const; + bool none() const; + dynamic_bitset operator~() const; + size_type count() const; + + // subscript + reference operator[](size_type pos) { return reference(*this, pos); } + bool operator[](size_type pos) const + { + #if UNITY_EDITOR + if (pos < this->m_num_bits) + return test_(pos); + else + { + ErrorString("dynamic_bitset.test bit out of bounds"); + return false; + } + #else + AssertIf(pos >= this->m_num_bits); + return test_(pos); + #endif + } + + unsigned long to_ulong() const; + + size_type size() const; + size_type num_blocks() const; + + bool is_subset_of(const dynamic_bitset& a) const; + bool is_proper_subset_of(const dynamic_bitset& a) const; + + void m_zero_unused_bits(); + + +private: + void set_(size_type bit); + bool set_(size_type bit, bool val); + void reset_(size_type bit); + bool test_(size_type bit) const; + void set_block_(size_type blocknum, Block b); + +public: + + // This is templated on the whole String instead of just CharT, + // Traits, Alloc to avoid compiler bugs. + template <typename String> + void from_string(const String& s, typename String::size_type pos, + typename String::size_type rlen) + { + reset(); // bugfix [gps] + size_type const tot = std::min (rlen, s.length()); // bugfix [gps] + + // Assumes string contains only 0's and 1's + for (size_type i = 0; i < tot; ++i) { + if (s[pos + tot - i - 1] == '1') { + set_(i); + } else { + AssertIf(s[pos + tot - i - 1] != '0'); + } + } + } + +}; + +// Global Functions: + +// comparison +inline bool operator!=(const dynamic_bitset& a, + const dynamic_bitset& b); + +inline bool operator<=(const dynamic_bitset& a, + const dynamic_bitset& b); + +inline bool operator>(const dynamic_bitset& a, + const dynamic_bitset& b); + +inline bool operator>=(const dynamic_bitset& a, + const dynamic_bitset& b); + +// bitset operations +inline dynamic_bitset +operator&(const dynamic_bitset& b1, + const dynamic_bitset& b2); + +inline dynamic_bitset +operator|(const dynamic_bitset& b1, + const dynamic_bitset& b2); + +inline dynamic_bitset +operator^(const dynamic_bitset& b1, + const dynamic_bitset& b2); + +inline dynamic_bitset +operator-(const dynamic_bitset& b1, + const dynamic_bitset& b2); + + +template <typename String> +void +to_string(const dynamic_bitset& b, + String& s); + +template <typename BlockOutputIterator> +void +to_block_range(const dynamic_bitset& b, + BlockOutputIterator result); + +template <typename BlockIterator> +inline void +from_block_range(BlockIterator first, BlockIterator last, + dynamic_bitset& result); + + +//============================================================================= +// dynamic_bitset implementation + + +//----------------------------------------------------------------------------- +// constructors, etc. + +inline dynamic_bitset::dynamic_bitset() + : dynamic_bitset_base(0) { } + +inline dynamic_bitset:: +dynamic_bitset(size_type num_bits, unsigned long value) + : dynamic_bitset_base(num_bits) +{ + const size_type M = std::min(sizeof(unsigned long) * 8, num_bits); + for(size_type i = 0; i < M; ++i, value >>= 1) // [G.P.S.] to be optimized + if ( value & 0x1 ) + set_(i); +} + +// copy constructor +inline dynamic_bitset:: +dynamic_bitset(const dynamic_bitset& b) + : dynamic_bitset_base(b.size()) +{ + memcpy(this->m_bits, b.m_bits, this->m_num_blocks * sizeof(Block)); +} + +inline void dynamic_bitset:: +swap(dynamic_bitset& b) +{ + std::swap(this->m_bits, b.m_bits); + std::swap(this->m_num_bits, b.m_num_bits); + std::swap(this->m_num_blocks, b.m_num_blocks); +} + +inline dynamic_bitset& dynamic_bitset:: +operator=(const dynamic_bitset& b) +{ + dynamic_bitset tmp(b); + this->swap(tmp); + return *this; +} + +//----------------------------------------------------------------------------- +// size changing operations + +inline void dynamic_bitset:: +resize(size_type num_bits, bool value) +{ + if (num_bits == size()) + return; + if (num_bits == 0) + { + this->m_num_bits = 0; + this->m_num_blocks = 0; + delete this->m_bits; + this->m_bits = 0; + return; + } + size_type new_nblocks = this->calc_num_blocks(num_bits); + Block* d = new Block[new_nblocks]; + if (num_bits < size()) { // shrink + std::copy(this->m_bits, this->m_bits + new_nblocks, d); + std::swap(d, this->m_bits); + delete []d; + } else { // grow + std::copy(this->m_bits, this->m_bits + this->m_num_blocks, d); + Block val = value? ~static_cast<Block>(0) : static_cast<Block>(0); + std::fill(d + this->m_num_blocks, d + new_nblocks, val); + std::swap(d, this->m_bits); + for (std::size_t i = this->m_num_bits; + i < this->m_num_blocks * bits_per_block; ++i) + set_(i, value); + if (d != 0) + delete []d; + } + this->m_num_bits = num_bits; + this->m_num_blocks = this->calc_num_blocks(num_bits); + m_zero_unused_bits(); +} + +inline void dynamic_bitset:: +clear() +{ + if (this->m_bits != 0) { + delete this->m_bits; + this->m_bits = 0; + this->m_num_bits = 0; + this->m_num_blocks = 0; + } +} + + +inline void dynamic_bitset:: +push_back(bool bit) +{ + this->resize(this->size() + 1); + set_(this->size() - 1, bit); +} + +inline void dynamic_bitset:: +append(Block value) +{ + std::size_t old_size = size(); + resize(old_size + bits_per_block); + if (size() % bits_per_block == 0) + set_block_(this->m_num_blocks - 1, value); + else { + // G.P.S. to be optimized + for (std::size_t i = old_size; i < size(); ++i, value >>= 1) + set_(i, value & 1); + } +} + + +//----------------------------------------------------------------------------- +// bitset operations +inline dynamic_bitset& +dynamic_bitset::operator&=(const dynamic_bitset& rhs) +{ + AssertIf(size() != rhs.size()); + for (size_type i = 0; i < this->m_num_blocks; ++i) + this->m_bits[i] &= rhs.m_bits[i]; + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::operator|=(const dynamic_bitset& rhs) +{ + AssertIf(size() != rhs.size()); + for (size_type i = 0; i < this->m_num_blocks; ++i) + this->m_bits[i] |= rhs.m_bits[i]; + m_zero_unused_bits(); + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::operator^=(const dynamic_bitset& rhs) +{ + AssertIf(size() != rhs.size()); + for (size_type i = 0; i < this->m_num_blocks; ++i) + this->m_bits[i] ^= rhs.m_bits[i]; + m_zero_unused_bits(); + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::operator-=(const dynamic_bitset& rhs) +{ + AssertIf(size() != rhs.size()); + for (size_type i = 0; i < this->m_num_blocks; ++i) + this->m_bits[i] = this->m_bits[i] & ~rhs.m_bits[i]; + m_zero_unused_bits(); + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::operator<<=(size_type n) +{ + if (n >= this->m_num_bits) + return reset(); + //else + if (n > 0) + { + size_type const last = this->m_num_blocks - 1; // m_num_blocks is >= 1 + size_type const div = n / bits_per_block; // div is <= last + size_type const r = n % bits_per_block; + + // PRE: div != 0 or r != 0 + + if (r != 0) { + + block_type const rs = bits_per_block - r; + + for (size_type i = last-div; i>0; --i) { + this->m_bits[i+div] = (this->m_bits[i] << r) | (this->m_bits[i-1] >> rs); + } + this->m_bits[div] = this->m_bits[0] << r; + + } + else { + for (size_type i = last-div; i>0; --i) { + this->m_bits[i+div] = this->m_bits[i]; + } + this->m_bits[div] = this->m_bits[0]; + } + + + // div blocks are zero filled at the less significant end + std::fill(this->m_bits, this->m_bits+div, static_cast<block_type>(0)); + + + } + + return *this; + + +} + + + + + + + +// NOTE: this assumes that within a single block bits are +// numbered from right to left. G.P.S. +// +// static Block offset(size_type bit) +// { return bit % bits_per_block; } +// +// +// In the implementation below the 'if (r != 0)' is logically +// unnecessary. It's there as an optimization only: in fact +// for r==0 the first branch becomes the second one with the +// b[last-div] = b[last] >> r; statement that does the work of +// the last iteration. +// +inline +dynamic_bitset & dynamic_bitset::operator>>=(size_type n) { + if (n >= this->m_num_bits) { + return reset(); + } + //else + if (n>0){ + + size_type const last = this->m_num_blocks - 1; // m_num_blocks is >= 1 + size_type const div = n / bits_per_block; // div is <= last + size_type const r = n % bits_per_block; + + // PRE: div != 0 or r != 0 + + if (r != 0) { + + block_type const ls = bits_per_block - r; + + for (size_type i = div; i < last; ++i) { + this->m_bits[i-div] = (this->m_bits[i] >> r) | (this->m_bits[i+1] << ls); + } + // r bits go to zero + this->m_bits[last-div] = this->m_bits[last] >> r; + } + + else { + for (size_type i = div; i <= last; ++i) { + this->m_bits[i-div] = this->m_bits[i]; + } + // note the '<=': the last iteration 'absorbs' + // this->m_bits[last-div] = this->m_bits[last] >> 0; + } + + + + // div blocks are zero filled at the most significant end + std::fill(this->m_bits+(this->m_num_blocks-div), this->m_bits+this->m_num_blocks, static_cast<block_type>(0)); + } + + return *this; +} + + + + + + + +inline dynamic_bitset +dynamic_bitset::operator<<(size_type n) const +{ + dynamic_bitset r(*this); + return r <<= n; +} + +inline dynamic_bitset +dynamic_bitset::operator>>(size_type n) const +{ + dynamic_bitset r(*this); + return r >>= n; +} + + +//----------------------------------------------------------------------------- +// basic bit operations + +inline dynamic_bitset& +dynamic_bitset::set(size_type pos, bool val) +{ + AssertIf(pos >= this->m_num_bits); + set_(pos, val); + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::set() +{ + if (this->m_num_bits > 0) { + using namespace std; + memset(this->m_bits, ~0u, this->m_num_blocks * sizeof(this->m_bits[0])); + m_zero_unused_bits(); + } + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::reset(size_type pos) +{ + AssertIf(pos >= this->m_num_bits); + reset_(pos); + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::reset() +{ + if (this->m_num_bits > 0) { + using namespace std; + memset(this->m_bits, 0, this->m_num_blocks * sizeof(this->m_bits[0])); + } + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::flip(size_type pos) +{ + AssertIf(pos >= this->m_num_bits); + this->m_bits[this->word(pos)] ^= this->mask1(pos); + return *this; +} + +inline dynamic_bitset& +dynamic_bitset::flip() +{ + for (size_type i = 0; i < this->m_num_blocks; ++i) + this->m_bits[i] = ~this->m_bits[i]; + m_zero_unused_bits(); + return *this; +} + +inline bool dynamic_bitset::test(size_type pos) const +{ + #if UNITY_EDITOR + if (pos < this->m_num_bits) + return test_(pos); + else + { + ErrorString("dynamic_bitset.test bit out of bounds"); + return false; + } + #else + AssertIf(pos >= this->m_num_bits); + return test_(pos); + #endif +} + +inline bool dynamic_bitset::any() const +{ + for (size_type i = 0; i < this->m_num_blocks; ++i) + if (this->m_bits[i]) + return 1; + return 0; +} + +inline bool dynamic_bitset::none() const +{ + return !any(); +} + +inline dynamic_bitset +dynamic_bitset::operator~() const +{ + dynamic_bitset b(*this); + b.flip(); + return b; +} + + +/* snipped: [gps] + +The following is the straightforward implementation of count(), which +we leave here in a comment for documentation purposes. + +template <typename Block, typename Allocator> +typename dynamic_bitset::size_type +dynamic_bitset::count() const +{ + size_type sum = 0; + for (size_type i = 0; i != this->m_num_bits; ++i) + if (test_(i)) + ++sum; + return sum; +} + +The actual algorithm used is based on using a lookup +table. + + + The basic idea of the method is to pick up X bits at a time + from the internal array of blocks and consider those bits as + the binary representation of a number N. Then, to use a table + of 1<<X elements where table[N] is the number of '1' digits + in the binary representation of N (i.e. in our X bits). + + Note that the table can be oversized (i.e. can even have more + than 1<<X elements; in that case only the first 1<<X will be + actually used) but it cannot be undersized. + In this implementation X is 8 (but can be easily changed: you + just have to change the definition of count<>::max_bits) and + the internal array of blocks is seen as an array of bytes: if + a byte has exactly 8 bits then it's enough to sum the value + of table[B] for each byte B. Otherwise 8 bits at a time are + 'extracted' from each byte by using another loop. As a further + efficiency consideration note that even if you have, let's say, + 32-bit chars the inner loop will not do 4 (i.e. 32/8) iterations, + unless you have at least one bit set in the highest 8 bits of the + byte. + + Note also that the outmost if/else is not necessary but is there + to help the optimizer (and one of the two branches is always dead + code). + + Aras: hardcoded table to be always max_bits=8. To help not so good compilers. + +*/ + + +inline dynamic_bitset::size_type +dynamic_bitset::count() const +{ + const byte_t * p = reinterpret_cast<const byte_t*>(this->m_bits); + const byte_t * past_end = p + this->m_num_blocks * sizeof(Block); + + size_type num = 0; + + while (p < past_end) { + num += bitcount<>::table[*p]; + ++p; + } + + return num; +} + + +//----------------------------------------------------------------------------- +// conversions + +// take as ref param instead? +template <typename CharT, typename Alloc> +void +to_string(const dynamic_bitset& b, + std::basic_string<CharT, Alloc>& s) +{ + s.assign(b.size(), '0'); + for (std::size_t i = 0; i < b.size(); ++i) + if (b.test(i)) // [G.P.S.] + s[b.size() - 1 - i] = '1'; +} + + +// Differently from to_string this function dumps out +// every bit of the internal representation (useful +// for debugging purposes) +// +template <typename CharT, typename Alloc> +void +dump_to_string(const dynamic_bitset& b, + std::basic_string<CharT, Alloc>& s) +{ + std::size_t const len = b.m_num_blocks * (dynamic_bitset::bits_per_block); + s.assign(len, '0'); + for (std::size_t i = 0; i != len; ++i) + if (b[i])// could use test_ here, but we have friend issues.-JGS + s[len - 1 - i] = '1'; +} + + + +template <typename BlockOutputIterator> +void +to_block_range(const dynamic_bitset& b, + BlockOutputIterator result) +{ + AssertIf(!(b.size() != 0 || b.num_blocks() == 0)); + std::copy (b.m_bits, b.m_bits + b.m_num_blocks, result); +} + +template <typename BlockIterator> +inline void +from_block_range(BlockIterator first, BlockIterator last, + dynamic_bitset& result) +{ + AssertIf(std::distance(first, last) != result.num_blocks()); + std::copy (first, last, result.m_bits); + result.m_zero_unused_bits (); +} + +inline dynamic_bitset::size_type +dynamic_bitset::size() const +{ + return this->m_num_bits; +} + +inline dynamic_bitset::size_type +dynamic_bitset::num_blocks() const +{ + return this->m_num_blocks; +} + +inline bool dynamic_bitset:: +is_subset_of(const dynamic_bitset& a) const +{ + AssertIf(this->size() != a.size()); + for (size_type i = 0; i < this->m_num_blocks; ++i) + if (this->m_bits[i] & ~a.m_bits[i]) + return false; + return true; +} + +inline bool dynamic_bitset:: +is_proper_subset_of(const dynamic_bitset& a) const +{ + AssertIf(this->size() != a.size()); + bool proper = false; + for (size_type i = 0; i < this->m_num_blocks; ++i) { + Block bt = this->m_bits[i], ba = a.m_bits[i]; + if (ba & ~bt) + proper = true; + if (bt & ~ba) + return false; + } + return proper; +} + +//----------------------------------------------------------------------------- +// comparison + +inline bool operator==(const dynamic_bitset& a, + const dynamic_bitset& b) +{ + using namespace std; + return (a.m_num_bits == b.m_num_bits) && + ((a.m_num_bits == 0) || + !memcmp(a.m_bits, b.m_bits, a.m_num_blocks * sizeof(a.m_bits[0]))); +} + +inline bool operator!=(const dynamic_bitset& a, + const dynamic_bitset& b) +{ + return !(a == b); +} + +inline bool operator<(const dynamic_bitset& a, + const dynamic_bitset& b) +{ + AssertIf(a.size() != b.size()); + typedef dynamic_bitset::size_type size_type; + + if (a.size() == 0) + return false; + + // Since we are storing the most significant bit + // at pos == size() - 1, we need to do the memcmp in reverse. + + // Compare a block at a time + for (size_type i = a.m_num_blocks - 1; i > 0; --i) + if (a.m_bits[i] < b.m_bits[i]) + return true; + else if (a.m_bits[i] > b.m_bits[i]) + return false; + + if (a.m_bits[0] < b.m_bits[0]) + return true; + else + return false; +} + +inline bool operator<=(const dynamic_bitset& a, + const dynamic_bitset& b) +{ + return !(a > b); +} + +inline bool operator>(const dynamic_bitset& a, + const dynamic_bitset& b) +{ + AssertIf(a.size() != b.size()); + typedef dynamic_bitset::size_type size_type; + + if (a.size() == 0) + return false; + + // Since we are storing the most significant bit + // at pos == size() - 1, we need to do the memcmp in reverse. + + // Compare a block at a time + for (size_type i = a.m_num_blocks - 1; i > 0; --i) + if (a.m_bits[i] < b.m_bits[i]) + return false; + else if (a.m_bits[i] > b.m_bits[i]) + return true; + + if (a.m_bits[0] > b.m_bits[0]) + return true; + else + return false; +} + +inline bool operator>=(const dynamic_bitset& a, + const dynamic_bitset& b) +{ + return !(a < b); +} + +//----------------------------------------------------------------------------- +// bitset operations + +inline dynamic_bitset +operator&(const dynamic_bitset& x, + const dynamic_bitset& y) +{ + dynamic_bitset b(x); + return b &= y; +} + +inline dynamic_bitset +operator|(const dynamic_bitset& x, + const dynamic_bitset& y) +{ + dynamic_bitset b(x); + return b |= y; +} + +inline dynamic_bitset +operator^(const dynamic_bitset& x, + const dynamic_bitset& y) +{ + dynamic_bitset b(x); + return b ^= y; +} + +inline dynamic_bitset +operator-(const dynamic_bitset& x, + const dynamic_bitset& y) +{ + dynamic_bitset b(x); + return b -= y; +} + + +//----------------------------------------------------------------------------- +// private member functions + +inline void dynamic_bitset:: +set_(size_type bit) +{ + this->m_bits[this->word(bit)] |= this->mask1(bit); +} + +inline void dynamic_bitset:: +set_block_(size_type blocknum, Block value) +{ + this->m_bits[blocknum] = value; +} + +inline void dynamic_bitset:: +reset_(size_type b) +{ + this->m_bits[this->word(b)] &= this->mask0(b); +} + +inline bool dynamic_bitset::test_(size_type b) const +{ + return (this->m_bits[this->word(b)] & this->mask1(b)) != static_cast<Block>(0); +} + +inline bool dynamic_bitset::set_(size_type n, bool value) +{ + if (value) + set_(n); + else + reset_(n); + return value != static_cast<Block>(0); +} + + +// If size() is not a multiple of bits_per_block +// then not all the bits in the last block are used. +// This function resets the unused bits (convenient +// for the implementation of many member functions) +// +inline void dynamic_bitset::m_zero_unused_bits() +{ + AssertIf (this->m_num_blocks != this->calc_num_blocks(this->m_num_bits)); + + // if != 0 this is the number of bits used in the last block + const size_type used_bits = this->m_num_bits % bits_per_block; + + if (used_bits != 0) + this->m_bits[this->m_num_blocks - 1] &= ~(~static_cast<Block>(0) << used_bits); + +} + +#endif diff --git a/Runtime/Utilities/dynamic_block_vector.h b/Runtime/Utilities/dynamic_block_vector.h new file mode 100644 index 0000000..302847b --- /dev/null +++ b/Runtime/Utilities/dynamic_block_vector.h @@ -0,0 +1,135 @@ +#pragma once + +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Utilities/dynamic_array.h" + +// dynamic_block_vector +// +// Allocates dynamic_arrays to hold the data in small blocks. +// Growing pushbacks allocates one new block at a time. +// Calls inplace constructor on all elements, and destroys the elements when removing from the list +// Resize preserves the elements in the list and pushes default initialized elements to reach size +// If resizing to something smaller, elements are popped (and destroyed) from the vector + + +template <typename T> +struct dynamic_block_vector +{ +private: + typedef dynamic_array<T> internal_container; + typedef dynamic_array<internal_container*> container; + +public: + + dynamic_block_vector (size_t allocationBlockSize) + : m_blockSize(allocationBlockSize), m_size(0), m_label(kMemDynamicArrayId, GET_CURRENT_ALLOC_ROOT_HEADER()) + { + } + + dynamic_block_vector (size_t allocationBlockSize, MemLabelId label) + : m_blockSize(allocationBlockSize), m_size(0), m_label(label) + { + } + + dynamic_block_vector (const dynamic_block_vector& rhs) + : m_blockSize(rhs.m_blockSize), m_size(0), m_label(rhs.m_label) + { + *this = rhs; + } + + ~dynamic_block_vector () + { + clear(); + } + + void clear() + { + for(int i = 0; i < m_size; i++) + (*this)[i].~T(); + + for(int i = 0; i < m_data.size(); i++) + UNITY_DELETE(m_data[i],m_label); + + m_data.clear(); + m_size = 0; + } + + void resize (size_t size) + { + while (m_size < size) + push_back(); + while (m_size > size) + pop_back(); + } + + dynamic_block_vector& operator=(const dynamic_block_vector& other) + { + if(this == &other) + return *this; + + clear(); + for( int i = 0; i < other.size(); i++) + push_back(other[i]); + return *this; + } + + template<class Iter> + void assign (Iter first, Iter last) + { + clear(); + for( ; first != last; ++first) + push_back(*first); + } + + void push_back () + { + int outerindex = m_size/m_blockSize; + int innerindex = m_size%m_blockSize; + if(outerindex == m_data.size()) + { + m_data.push_back(UNITY_NEW(internal_container,m_label)(m_blockSize,m_label)); + } + new (&(*m_data[outerindex])[innerindex]) T(); + m_size++; + } + + void push_back (const T& t) + { + int outerindex = m_size/m_blockSize; + int innerindex = m_size%m_blockSize; + if(outerindex == m_data.size()) + { + m_data.push_back(UNITY_NEW(internal_container,m_label)(m_blockSize,m_label)); + } + new (&(*m_data[outerindex])[innerindex]) T(t); + m_size++; + } + + void pop_back () + { + (*this)[m_size-1].~T(); + m_size--; + int outersize = m_size/m_blockSize + 1; + if (outersize < m_data.size()) + { + UNITY_DELETE(m_data.back(),m_label); + m_data.pop_back(); + } + } + + size_t size () const { return m_size; } + + T& back() { Assert (m_size != 0); return (*this)[m_size - 1]; } + + T const& operator[] (size_t index) const { DebugAssert(index < m_size); return (*m_data[index/m_blockSize])[index%m_blockSize]; } + T& operator[] (size_t index) { DebugAssert(index < m_size); return (*m_data[index/m_blockSize])[index%m_blockSize]; } + +private: + + container m_data; + MemLabelId m_label; + size_t m_size; + size_t m_blockSize; +}; + + diff --git a/Runtime/Utilities/fixed_bitset.h b/Runtime/Utilities/fixed_bitset.h new file mode 100644 index 0000000..b805ae7 --- /dev/null +++ b/Runtime/Utilities/fixed_bitset.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Runtime/Utilities/StaticAssert.h" + + +// Fixed size bitset; size (N) must be a multiple of 32. +// Similar to dynamic_bitset, but does not do dynamic allocations and stuff. +template<int N> +class fixed_bitset { +public: + enum { kBlockSize = 32, kBlockCount = N/kBlockSize }; +public: + fixed_bitset() + { + CompileTimeAssert(N % kBlockSize == 0, "size should be multiple fo 32"); + CompileTimeAssert(sizeof(m_Bits[0])*8 == kBlockSize, "size of internal array type should be 4" ); + for( int i = 0; i < kBlockCount; ++i ) + m_Bits[i] = 0; + } + // note: default copy constructor and assignment operator are ok + + void set( int index ) { + AssertIf( index < 0 || index >= N ); + m_Bits[index/kBlockSize] |= 1 << (index & (kBlockSize-1)); + } + void reset( int index ) { + AssertIf( index < 0 || index >= N ); + m_Bits[index/kBlockSize] &= ~( 1 << (index & (kBlockSize-1)) ); + } + bool test( int index ) const { + AssertIf( index < 0 || index >= N ); + return m_Bits[index/kBlockSize] & ( 1 << (index & (kBlockSize-1)) ); + } + void reset_all() { + memset( m_Bits, 0, sizeof(m_Bits) ); + } + + bool operator==( const fixed_bitset<N>& o ) const { + for( int i = 0; i < kBlockCount; ++i ) + if( m_Bits[i] != o.m_Bits[i] ) + return false; + return true; + } + bool operator!=( const fixed_bitset<N>& o ) const { + for( int i = 0; i < kBlockCount; ++i ) + if( m_Bits[i] == o.m_Bits[i] ) + return false; + return true; + } + +private: + UInt32 m_Bits[kBlockCount]; +}; diff --git a/Runtime/Utilities/sorted_vector.h b/Runtime/Utilities/sorted_vector.h new file mode 100644 index 0000000..5f0576f --- /dev/null +++ b/Runtime/Utilities/sorted_vector.h @@ -0,0 +1,358 @@ +#ifndef SORTED_VECTOR_H +#define SORTED_VECTOR_H +#include <vector> +#include <algorithm> + +/// container optimization anschauen (compressed pair for valuecompare) + + +template<class T, class Compare, class Allocator> +class sorted_vector : private Compare +{ + public: + + typedef std::vector<T, Allocator> container; + typedef typename container::iterator iterator; + typedef typename container::const_iterator const_iterator; + typedef typename container::reverse_iterator reverse_iterator; + typedef typename container::const_reverse_iterator const_reverse_iterator; + typedef typename container::value_type value_type; + typedef typename container::size_type size_type; + typedef typename container::difference_type difference_type; + typedef Compare value_compare; + typedef typename Allocator::reference reference; + typedef typename Allocator::const_reference const_reference; + typedef Allocator allocator_type; + + value_compare const& get_compare () const { return *static_cast<value_compare const*> (this); } + + + sorted_vector (const Compare& comp, const Allocator& a) + : c (a), value_compare (comp) {} + + + bool empty() const { return c.empty(); } + size_type size() const { return c.size(); } + const value_type& front() const { return c.front(); } + const value_type& back() const { return c.front(); } + + const_iterator begin ()const { return c.begin (); } + const_iterator end ()const { return c.end (); } + iterator begin () { return c.begin (); } + iterator end () { return c.end (); } + const_reverse_iterator rbegin ()const { return c.rbegin (); } + const_reverse_iterator rend ()const { return c.rend (); } + reverse_iterator rbegin () { return c.rbegin (); } + reverse_iterator rend () { return c.rend (); } + + value_compare value_comp() const { return get_compare ();} +/* + template <class InputIterator> + void insert_one (InputIterator first, InputIterator last) + { + c.reserve (size () + std::distance (first, last)); + for (;first != last;first++) + insert_one (*i); + } + + template<typename KeyT, typename MappedT> + MappedT& find_or_insert (const KeyT& k) + { + iterator i = lower_bound (k); + if (i == end () || get_compare () (k, *i)) + return c.insert (i, std::make_pair<KeyT, MappedT> (k, MappedT ()))->second; + else + return i->second; + } +*/ + + template<typename KeyT, typename MappedT> + void find_or_insert (MappedT*& mappedT, const KeyT& k) + { + iterator i = lower_bound (k); + if (i == end () || get_compare () (k, *i)) + #if _MSC_VER >= 1700 // Microsoft Visual Studio 2012 workaround + mappedT = &c.insert (i, std::make_pair<KeyT, MappedT> (static_cast<KeyT&&>(const_cast<KeyT&>(k)), MappedT ()))->second; + #else + mappedT = &c.insert (i, std::make_pair<KeyT, MappedT> (k, MappedT ()))->second; + #endif + else + mappedT = &i->second; + } + + std::pair<iterator, bool> insert_one (const value_type& x) + { + iterator i = lower_bound (x); + // is not included in container + if (i == end () || get_compare () (x, *i)) + return std::make_pair (c.insert (i, x), true); + else + return std::make_pair (i, false); + + } + + void verify_duplicates_and_sorted () const + { + #if DEBUGMODE + // Check that there are no duplicates in the set + if (empty()) + return; + + const_iterator previous = c.begin(); + const_iterator i = c.begin(); + i++; + for (; i != c.end();++i) + { + Assert(get_compare ()(*previous, *i)); + previous = i; + } + #endif + } + + + template<class CompareType> + size_type erase_one (const CompareType& x) + { + iterator i = lower_bound (x); + if (i == end () || get_compare () (x, *i)) + return 0; + else + { + c.erase (i); + return 1; + } + } + + void erase(iterator position) { c.erase (position);} + void erase(iterator first, iterator last) { c.erase (first, last); } + void swap(sorted_vector& x) { c.swap (x.c); } + + void clear () { c.clear (); } + + template<class T2> + size_type count_one (const T2& x)const + { + const_iterator i = lower_bound (x); + if (i == end () || get_compare () (x, *i)) + return 0; + else + return 1; + } + + template<class T2> + iterator find (const T2& x) + { + iterator i = lower_bound (x); + if (i == end () || get_compare () (x, *i)) + return end (); + else + return i; + } + + template<class T2> + const_iterator find (const T2& x) const + { + const_iterator i = lower_bound (x); + if (i == end () || get_compare () (x, *i)) + return end (); + else + return i; + } + + template<class T2> + iterator lower_bound (const T2& x) + { + return std::lower_bound (c.begin (), c.end (), x, get_compare ()); + } + + template<class T2> + const_iterator lower_bound (const T2& x) const + { + return std::lower_bound (c.begin (), c.end (), x, get_compare ()); + } + + template<class T2> + iterator upper_bound (const T2& x) + { + return std::lower_bound (c.begin (), c.end (), x, get_compare ()); + } + + template<class T2> + const_iterator upper_bound (const T2& x) const + { + return std::lower_bound (c.begin (), c.end (), x, get_compare ()); + } + + template<class T2> + std::pair<iterator, iterator> equal_range (const T2& x) + { + return std::equal_range (c.begin (), c.end (), x, get_compare ()); + } + + template<class T2> + std::pair<const_iterator, const_iterator> equal_range (const T2& x) const + { + return std::equal_range (c.begin (), c.end (), x, get_compare ()); + } + + void reserve (size_type n) { c.reserve (n); } + + value_type& operator [] (int n) { return c[n]; } + const value_type& operator [] (int n) const { return c[n]; } + + public: + + container c; +}; + +template<class T, class Compare, class Allocator> +class unsorted_vector : private Compare +{ + public: + + typedef std::vector<T, Allocator> container; + typedef typename container::iterator iterator; + typedef typename container::const_iterator const_iterator; + typedef typename container::reverse_iterator reverse_iterator; + typedef typename container::const_reverse_iterator const_reverse_iterator; + + typedef typename container::value_type value_type; + typedef typename container::size_type size_type; + typedef typename container::difference_type difference_type; + typedef Compare value_compare; + typedef typename Allocator::reference reference; + typedef typename Allocator::const_reference const_reference; + typedef Allocator allocator_type; + + value_compare const& get_compare () const { return *static_cast<value_compare const*> (this); } + + unsorted_vector (const Compare& comp, const Allocator& a) + : c (a), value_compare (comp) {} + + + bool empty() const { return c.empty(); } + size_type size() const { return c.size(); } + const value_type& front() const { return c.front(); } + const value_type& back() const { return c.front(); } + + const_iterator begin ()const { return c.begin (); } + const_iterator end ()const { return c.end (); } + iterator begin () { return c.begin (); } + iterator end () { return c.end (); } + const_reverse_iterator rbegin ()const { return c.rbegin (); } + const_reverse_iterator rend ()const { return c.rend (); } + reverse_iterator rbegin () { return c.rbegin (); } + reverse_iterator rend () { return c.rend (); } + + value_compare value_comp() const { return get_compare ();} +/* + template <class InputIterator> + void insert_one (InputIterator first, InputIterator last) + { + c.reserve (size () + std::distance (first, last)); + for (;first != last;first++) + insert_one (*i); + } +*/ + template<typename KeyT, typename MappedT> + void find_or_insert (MappedT*& mappedT, const KeyT& k) + { + iterator i = find (k); + if (i == end ()) + { + c.push_back (std::make_pair<KeyT, MappedT> (k, MappedT ())); + mappedT = &(c.end () - 1)->second; + } + else + mappedT = &i->second; + } + /* + template<class Key, class Value> + Value& find_or_insert (const Key& k) + { + iterator i = find (k); + if (i == end ()) + { + c.push_back (std::make_pair<Key, Value> (k, Value ())); + return (c.end () - 1)->second; + } + else + return i->second; + } + */ + std::pair<iterator, bool> insert_one (const value_type& x) + { + iterator i = find (x); + // is not included in container + if (i == end ()) + { + c.push_back (x); + return std::make_pair (c.end () - 1, true); + } + else + return std::make_pair (i, false); + } + + template<class CompareType> + size_type erase_one (const CompareType& x) + { + iterator i = find (x); + if (i == end ()) + return 0; + else + { + erase (i); + return 1; + } + } + + void erase(iterator position) { *position = c.back (); c.pop_back (); } + void swap(unsorted_vector& x) { c.swap (x.c); } + + void clear () { c.clear (); } + + template<class T2> + size_type count_one (const T2& x)const + { + const_iterator i = find (x); + return i != end (); + } + + template<class T2> + iterator find (const T2& x) + { + iterator b = c.begin (); + iterator e = c.end (); + for (;b != e;++b) + { + if (get_compare () (*b, x)) + return b; + } + return e; + } + + template<class T2> + const_iterator find (const T2& x) const + { + { + const_iterator b = c.begin (); + const_iterator e = c.end (); + for (;b != e;++b) + { + if (get_compare () (*b, x)) + return b; + } + return e; + } + + } + void reserve (size_type n) { c.reserve (n); } + value_type& operator [] (int n) { return c[n]; } + const value_type& operator [] (int n) const { return c[n]; } + + public: + + container c; +}; + +#endif diff --git a/Runtime/Utilities/triple.h b/Runtime/Utilities/triple.h new file mode 100644 index 0000000..4a3d5c1 --- /dev/null +++ b/Runtime/Utilities/triple.h @@ -0,0 +1,46 @@ +#pragma once + +// TEMPLATE STRUCT triple +template<class T> +struct triple +{ + // store a triple of values of the same type + + triple() + : first(T()), second(T()), third(T()) + { // construct from defaults + } + + triple(const T& _Val1, const T& _Val2, const T& _Val3) + : first(_Val1), second(_Val2), third(_Val3) + { // construct from specified values + } + + template<class otherT> + triple(const triple<otherT>& _Right) + : first(_Right.first), second(_Right.second), third(_Right.third) + { // construct from a compatible triple + } + + T first; // the first stored value + T second; // the second stored value + T third; // the third stored value +}; + +template<class T> +inline bool operator==(const triple<T>& _Left, const triple<T>& _Right) +{ // test for triple equality + return (_Left.first == _Right.first && _Left.second == _Right.second && _Left.third == _Right.third); +} + +template<class T> +inline bool operator!=(const triple<T>& _Left, const triple<T>& _Right) +{ // test for triple inequality + return (!(_Left == _Right)); +} + +template<class T> +inline triple<T> make_triple(T _Val1, T _Val2, T _Val3) +{ // return a triple composed from arguments + return (triple<T>(_Val1, _Val2, _Val3)); +}
\ No newline at end of file diff --git a/Runtime/Utilities/type_traits.h b/Runtime/Utilities/type_traits.h new file mode 100644 index 0000000..edffc34 --- /dev/null +++ b/Runtime/Utilities/type_traits.h @@ -0,0 +1,250 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ---- +// Author: Matt Austern +// +// Define a small subset of tr1 type traits. The traits we define are: +// is_integral +// is_floating_point +// is_pointer +// is_reference +// is_pod +// has_trivial_constructor +// has_trivial_copy +// has_trivial_assign +// has_trivial_destructor +// remove_const +// remove_volatile +// remove_cv +// remove_reference +// remove_pointer +// is_convertible +// We can add more type traits as required. + +#ifndef BASE_TYPE_TRAITS_H_ +#define BASE_TYPE_TRAITS_H_ + +//#include <google/sparsehash/sparseconfig.h> +#include <utility> // For pair + +namespace dense_hash_map_traits +{ + +// integral_constant, defined in tr1, is a wrapper for an integer +// value. We don't really need this generality; we could get away +// with hardcoding the integer type to bool. We use the fully +// general integer_constant for compatibility with tr1. + +template<class T, T v> +struct integral_constant { + static const T value = v; + typedef T value_type; + typedef integral_constant<T, v> type; +}; + +template <class T, T v> const T integral_constant<T, v>::value; + +// Abbreviations: true_type and false_type are structs that represent +// boolean true and false values. +typedef integral_constant<bool, true> true_type; +typedef integral_constant<bool, false> false_type; + +// Types small_ and big_ are guaranteed such that sizeof(small_) < +// sizeof(big_) +typedef char small_; + +struct big_ { + char dummy[2]; +}; + +// is_integral is false except for the built-in integer types. +template <class T> struct is_integral : false_type { }; +template<> struct is_integral<bool> : true_type { }; +template<> struct is_integral<char> : true_type { }; +template<> struct is_integral<unsigned char> : true_type { }; +template<> struct is_integral<signed char> : true_type { }; +#if defined(_MSC_VER) +// wchar_t is not by default a distinct type from unsigned short in +// Microsoft C. +// See http://msdn2.microsoft.com/en-us/library/dh8che7s(VS.80).aspx +template<> struct is_integral<__wchar_t> : true_type { }; +#else +template<> struct is_integral<wchar_t> : true_type { }; +#endif +template<> struct is_integral<short> : true_type { }; +template<> struct is_integral<unsigned short> : true_type { }; +template<> struct is_integral<int> : true_type { }; +template<> struct is_integral<unsigned int> : true_type { }; +template<> struct is_integral<long> : true_type { }; +template<> struct is_integral<unsigned long> : true_type { }; +#ifdef HAVE_LONG_LONG +template<> struct is_integral<long long> : true_type { }; +template<> struct is_integral<unsigned long long> : true_type { }; +#endif + + +// is_floating_point is false except for the built-in floating-point types. +template <class T> struct is_floating_point : false_type { }; +template<> struct is_floating_point<float> : true_type { }; +template<> struct is_floating_point<double> : true_type { }; +template<> struct is_floating_point<long double> : true_type { }; + + +// is_pointer is false except for pointer types. +template <class T> struct is_pointer : false_type { }; +template <class T> struct is_pointer<T*> : true_type { }; + + +// is_reference is false except for reference types. +template<typename T> struct is_reference : false_type {}; +template<typename T> struct is_reference<T&> : true_type {}; + + +// We can't get is_pod right without compiler help, so fail conservatively. +// We will assume it's false except for arithmetic types and pointers, +// and const versions thereof. Note that std::pair is not a POD. +template <class T> struct is_pod + : integral_constant<bool, (is_integral<T>::value || + is_floating_point<T>::value || + is_pointer<T>::value)> { }; +template <class T> struct is_pod<const T> : is_pod<T> { }; + + +// We can't get has_trivial_constructor right without compiler help, so +// fail conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial +// constructors. (3) array of a type with a trivial constructor. +// (4) const versions thereof. +template <class T> struct has_trivial_constructor : is_pod<T> { }; +template <class T, class U> struct has_trivial_constructor<std::pair<T, U> > + : integral_constant<bool, + (has_trivial_constructor<T>::value && + has_trivial_constructor<U>::value)> { }; +template <class A, int N> struct has_trivial_constructor<A[N]> + : has_trivial_constructor<A> { }; +template <class T> struct has_trivial_constructor<const T> + : has_trivial_constructor<T> { }; + +// We can't get has_trivial_copy right without compiler help, so fail +// conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial copy +// constructors. (3) array of a type with a trivial copy constructor. +// (4) const versions thereof. +template <class T> struct has_trivial_copy : is_pod<T> { }; +template <class T, class U> struct has_trivial_copy<std::pair<T, U> > + : integral_constant<bool, + (has_trivial_copy<T>::value && + has_trivial_copy<U>::value)> { }; +template <class A, int N> struct has_trivial_copy<A[N]> + : has_trivial_copy<A> { }; +template <class T> struct has_trivial_copy<const T> : has_trivial_copy<T> { }; + +// We can't get has_trivial_assign right without compiler help, so fail +// conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial copy +// constructors. (3) array of a type with a trivial assign constructor. +template <class T> struct has_trivial_assign : is_pod<T> { }; +template <class T, class U> struct has_trivial_assign<std::pair<T, U> > + : integral_constant<bool, + (has_trivial_assign<T>::value && + has_trivial_assign<U>::value)> { }; +template <class A, int N> struct has_trivial_assign<A[N]> + : has_trivial_assign<A> { }; + +// We can't get has_trivial_destructor right without compiler help, so +// fail conservatively. We will assume it's false except for: (1) types +// for which is_pod is true. (2) std::pair of types with trivial +// destructors. (3) array of a type with a trivial destructor. +// (4) const versions thereof. +template <class T> struct has_trivial_destructor : is_pod<T> { }; +template <class T, class U> struct has_trivial_destructor<std::pair<T, U> > + : integral_constant<bool, + (has_trivial_destructor<T>::value && + has_trivial_destructor<U>::value)> { }; +template <class A, int N> struct has_trivial_destructor<A[N]> + : has_trivial_destructor<A> { }; +template <class T> struct has_trivial_destructor<const T> + : has_trivial_destructor<T> { }; + +// Specified by TR1 [4.7.1] +template<typename T> struct remove_const { typedef T type; }; +template<typename T> struct remove_const<T const> { typedef T type; }; +template<typename T> struct remove_volatile { typedef T type; }; +template<typename T> struct remove_volatile<T volatile> { typedef T type; }; +template<typename T> struct remove_cv { + typedef typename remove_const<typename remove_volatile<T>::type>::type type; +}; + + +// Specified by TR1 [4.7.2] +template<typename T> struct remove_reference { typedef T type; }; +template<typename T> struct remove_reference<T&> { typedef T type; }; + +// Specified by TR1 [4.7.4] Pointer modifications. +template<typename T> struct remove_pointer { typedef T type; }; +template<typename T> struct remove_pointer<T*> { typedef T type; }; +template<typename T> struct remove_pointer<T* const> { typedef T type; }; +template<typename T> struct remove_pointer<T* volatile> { typedef T type; }; +template<typename T> struct remove_pointer<T* const volatile> { + typedef T type; }; + +// Specified by TR1 [4.6] Relationships between types +#ifndef _MSC_VER +namespace internal_type_traits { + +// This class is an implementation detail for is_convertible, and you +// don't need to know how it works to use is_convertible. For those +// who care: we declare two different functions, one whose argument is +// of type To and one with a variadic argument list. We give them +// return types of different size, so we can use sizeof to trick the +// compiler into telling us which function it would have chosen if we +// had called it with an argument of type From. See Alexandrescu's +// _Modern C++ Design_ for more details on this sort of trick. + +template <typename From, typename To> +struct ConvertHelper { + static small_ Test(To); + static big_ Test(...); + static From Create(); +}; +} // namespace internal + +// Inherits from true_type if From is convertible to To, false_type otherwise. +template <typename From, typename To> +struct is_convertible + : integral_constant<bool, + sizeof(internal_type_traits::ConvertHelper<From, To>::Test( + internal_type_traits::ConvertHelper<From, To>::Create())) + == sizeof(small_)> { +}; +#endif + +} +#endif // BASE_TYPE_TRAITS_H_ diff --git a/Runtime/Utilities/vector_map.h b/Runtime/Utilities/vector_map.h new file mode 100644 index 0000000..9d3c936 --- /dev/null +++ b/Runtime/Utilities/vector_map.h @@ -0,0 +1,268 @@ +#ifndef VECTOR_MAP_H +#define VECTOR_MAP_H + +#include "sorted_vector.h" +#include <functional> + +// vector_map offers the same functionality as std::set +// but it is implemented using sorted vectors. +// sorted_vectors are smaller in used memory and can be faster due to cache coherence +// However inserting or erasing elements can be O (N) instead of O (logN) +// Usually you will want to use vector_set when you have a set which you use +// much more often to find values than inserting them or if the set you use is very small +// vector_map also offers the vector function reserve. +// - also note that if you store an iterator to an element you are NOT guaranteed that this iterator +// remains valid after you insert/erase other elements +// - vector_map«s key is not const, but you are still not allowed to change the key without erasing/inserting it. + + +template<class Key, class T, class Compare = std::less<Key>, + class Allocator = std::allocator<std::pair<Key, T> > > +class vector_map +{ + public: + + typedef Key key_type; + typedef T mapped_type; + typedef std::pair<Key,T> value_type; + typedef Compare key_compare; + typedef Allocator allocator_type; + typedef typename Allocator::reference reference; + typedef typename Allocator::const_reference const_reference; + typedef typename Allocator::size_type size_type; + typedef typename Allocator::difference_type difference_type; + typedef typename Allocator::pointer pointer; + typedef typename Allocator::const_pointer const_pointer; + + class value_compare + : public std::binary_function<value_type,value_type,bool> + { + public: + bool operator()(const value_type& x, const value_type& y) const + { + return comp(x.first, y.first); + } + bool operator()(const key_type& x, const value_type& y) const + { + return comp(x, y.first); + } + bool operator()(const value_type& x, const key_type& y) const + { + return comp(x.first, y); + } + + value_compare() {} + value_compare(Compare c) : comp(c) {} + protected: + Compare comp; + + + friend class vector_map; + }; + + typedef sorted_vector<value_type, value_compare, Allocator> container; + typedef typename container::container vector_container; + typedef typename container::iterator iterator; + typedef typename container::const_iterator const_iterator; + typedef typename container::reverse_iterator reverse_iterator; + typedef typename container::const_reverse_iterator const_reverse_iterator; + + public: + + // ctors + vector_map (const Compare& comp = Compare (), const Allocator& a = Allocator ()) + : c (value_compare(comp), a) + { } + + template <class InputIterator> + vector_map(InputIterator first, InputIterator last, const Compare& comp = Compare (), const Allocator& a = Allocator ()) + : c (value_compare(comp), a) + { insert_one (first, last); } + + // iterators + iterator begin() { return c.begin (); } + const_iterator begin() const { return c.begin (); } + iterator end() { return c.end (); } + const_iterator end() const { return c.end (); } + reverse_iterator rbegin() { return c.rbegin (); } + const_reverse_iterator rbegin() const { return c.rbegin (); } + reverse_iterator rend() { return c.rend (); } + const_reverse_iterator rend() const { return c.rend (); } + + // capacity: + bool empty() const { return c.empty (); } + size_type size() const { return c.size (); } + size_type max_size() const { return c.max_size (); } + + // modifiers: + std::pair<iterator, bool> insert(const value_type& x) { return c.insert_one (x); } + + template <class InputIterator> + void insert(InputIterator first, InputIterator last) { c.insert_one(first, last); } + + void erase(iterator position) { c.erase (position); } + size_type erase(const key_type& x) { return c.erase_one (x); } + void erase(iterator first, iterator last) { c.erase (first, last); } + void swap(vector_map& x) { c.swap (x.c); } + void clear() { c.clear (); } + + // observers: + key_compare key_comp() const { return c.value_comp ().comp; } + value_compare value_comp() const { return c.value_comp (); } + + // lib.map.ops map operations: + // CW has problems with the straightforward version +// mapped_type& operator[] (const key_type& x) { return c.find_or_insert<key_type, mapped_type> (x); } + mapped_type& operator[] (const key_type& x) { mapped_type* temp; c.find_or_insert (temp, x); return *temp; } + + iterator find(const key_type& x) { return c.find (x); } + const_iterator find(const key_type& x) const { return c.find (x); } + size_type count(const key_type& x) const { return c.count_one (x); } + + iterator lower_bound(const key_type& x) { return c.lower_bound (x); } + const_iterator lower_bound(const key_type& x) const{ return c.lower_bound (x); } + iterator upper_bound(const key_type& x) { return c.upper_bound (x); } + const_iterator upper_bound(const key_type& x) const{ return c.upper_bound (x); } + + std::pair<iterator,iterator> equal_range(const key_type& x) { return c.equal_range (x); } + std::pair<const_iterator,const_iterator> equal_range(const key_type& x) const { return c.equal_range (x); } + + // vector specific operations + void reserve (size_type n) { c.reserve (n); } + + vector_container& get_vector () { return c.c; } + + void push_unsorted (const key_type& x, const mapped_type& value) + { + get_vector().push_back(std::make_pair(x, value)); + } + + void sort () + { + std::sort(c.c.begin(), c.c.end(), c.value_comp ()); + c.verify_duplicates_and_sorted (); + } + + void verify_duplicates_and_sorted () const + { + c.verify_duplicates_and_sorted (); + } + + private: + + container c; +}; + +template<class Key, class T, class Compare = std::equal_to<Key>, + class Allocator = std::allocator<std::pair<Key, T> > > +class us_vector_map +{ + public: + + typedef Key key_type; + typedef T mapped_type; + typedef std::pair<Key,T> value_type; + typedef Compare key_compare; + typedef Allocator allocator_type; + typedef typename Allocator::reference reference; + typedef typename Allocator::const_reference const_reference; + typedef typename Allocator::size_type size_type; + typedef typename Allocator::difference_type difference_type; + typedef typename Allocator::pointer pointer; + typedef typename Allocator::const_pointer const_pointer; + + class value_compare + : public std::binary_function<value_type,value_type,bool> + { + public: + bool operator()(const value_type& x, const value_type& y) const + { + return comp(x.first, y.first); + } + bool operator()(const key_type& x, const value_type& y) const + { + return comp(x, y.first); + } + bool operator()(const value_type& x, const key_type& y) const + { + return comp(x.first, y); + } + protected: + Compare comp; + + value_compare() {} + value_compare(Compare c) : comp(c) {} + + friend class us_vector_map; + }; + + typedef unsorted_vector<value_type, value_compare, Allocator> container; + typedef typename container::container vector_container; + typedef typename container::iterator iterator; + typedef typename container::const_iterator const_iterator; + typedef typename container::reverse_iterator reverse_iterator; + typedef typename container::const_reverse_iterator const_reverse_iterator; + + public: + + // ctors + us_vector_map (const Compare& comp = Compare (), const Allocator& a = Allocator ()) + : c (value_compare(comp), a) + { } + + template <class InputIterator> + us_vector_map(InputIterator first, InputIterator last, const Compare& comp = Compare (), const Allocator& a = Allocator ()) + : c (value_compare(comp), a) + { insert_one (first, last); } + + // iterators + iterator begin() { return c.begin (); } + const_iterator begin() const { return c.begin (); } + iterator end() { return c.end (); } + const_iterator end() const { return c.end (); } + reverse_iterator rbegin() { return c.rbegin (); } + const_reverse_iterator rbegin() const { return c.rbegin (); } + reverse_iterator rend() { return c.rend (); } + const_reverse_iterator rend() const { return c.rend (); } + + + // capacity: + bool empty() const { return c.empty (); } + size_type size() const { return c.size (); } + size_type max_size() const { return c.max_size (); } + + // modifiers: + std::pair<iterator, bool> insert(const value_type& x){ return c.insert_one (x); } + + template <class InputIterator> + void insert(InputIterator first, InputIterator last) { c.insert_one(first, last); } + + void erase(iterator position) { c.erase (position); } + size_type erase(const key_type& x) { return c.erase_one (x); } + void swap(us_vector_map& x) { c.swap (x.c);} + void clear() { c.clear (); } + + // observers: + key_compare key_comp() const { return c.value_comp ().comp; } + value_compare value_comp() const { return c.value_comp (); } + + // lib.map.ops map operations: + // CW has problems with the straightforward version + // mapped_type& operator[] (const key_type& x) { return c.find_or_insert<key_type, mapped_type> (x); } + mapped_type& operator[] (const key_type& x) { mapped_type* temp; c.find_or_insert (temp, x); return *temp; } + + iterator find(const key_type& x) { return c.find (x); } + const_iterator find(const key_type& x) const { return c.find (x); } + size_type count(const key_type& x) const { return c.count_one (x); } + + // vector specific operations + void reserve (size_type n) { c.reserve (n); } + + vector_container& get_vector () { return c.c; } + + private: + + container c; +}; + +#endif diff --git a/Runtime/Utilities/vector_set.h b/Runtime/Utilities/vector_set.h new file mode 100644 index 0000000..9d06e19 --- /dev/null +++ b/Runtime/Utilities/vector_set.h @@ -0,0 +1,229 @@ +#ifndef VECTOR_SET_H +#define VECTOR_SET_H + +#include "sorted_vector.h" +#include <functional> + +// vector_set offers the same functionality as std::set +// but it is implemented using sorted vectors. +// sorted_vectors are smaller in used memory and can be faster due to cache coherence +// However inserting or erasing elements is O (N) instead of O (logN) +// - Usually you will want to use vector_set when you have a set which you use +// much more often to find values than inserting them or if the set you use is very small +// vector_set also offers the vector function reserve. +// - also note that if you store an iterator to an element you are NOT guaranteed that this iterator +// remains valid after you insert/erase other elements + +template <class Key, class Compare = std::less<Key>, class Allocator = std::allocator<Key> > +class vector_set +{ + public: + + typedef sorted_vector<Key, Compare, Allocator> container; + typedef typename container::container vector_container; + typedef typename container::iterator iterator; + typedef typename container::const_iterator const_iterator; + typedef typename container::reverse_iterator reverse_iterator; + typedef typename container::const_reverse_iterator const_reverse_iterator; + typedef typename container::value_type value_type; + typedef typename container::value_type key_type; + typedef Compare key_compare; + typedef Compare value_compare; + typedef typename container::size_type size_type; + typedef typename container::difference_type difference_type; + typedef Allocator allocator_type; + + + vector_set (const Compare& comp = Compare (), const Allocator& a = Allocator()) + : c (comp, a) { } + + template <class InputIterator> + vector_set (InputIterator first, InputIterator last, const Compare& comp = Compare (), const Allocator& a = Allocator()) + : c (comp, a) + { + assign(first, last); + } + + // Assigns a range that is known to be sorted + template<class InputIterator> + void assign_sorted (InputIterator first, InputIterator last){ c.c.assign (first, last); c.verify_duplicates_and_sorted (); } + + // Assigns a range + // Asserts if there are any duplicates in the input + template<class InputIterator> + void assign (InputIterator first, InputIterator last) + { + c.c.assign (first, last); + + sort(); + } + + // Assigns a range + // clears any duplicate objects + template<class InputIterator> + void assign_clear_duplicates (InputIterator first, InputIterator last) + { + c.c.assign (first, last); + std::stable_sort(c.begin(), c.end(), c.value_comp()); + + // Check that there are no duplicates in the set + if (!empty()) + { + Key* previous = &*c.begin(); + iterator i = c.begin(); i++; + for (; i != c.end();) + { + if (*i < *previous || *previous < *i) + { + previous = &*i; + i++; + } + else + { + iterator e; + for (e=i;e!=c.end() && !(*e < *previous || *previous < *e);e++) + ; + c.erase(i, e); + } + } + } + + c.verify_duplicates_and_sorted (); + } + + bool empty () const { return c.empty (); } + + iterator begin() { return c.begin (); } + const_iterator begin() const { return c.begin (); } + iterator end() { return c.end (); } + const_iterator end() const { return c.end (); } + reverse_iterator rbegin() { return c.rbegin (); } + const_reverse_iterator rbegin() const { return c.rbegin (); } + reverse_iterator rend() { return c.rend (); } + const_reverse_iterator rend() const { return c.rend (); } + + size_type size() const { return c.size (); } + size_type max_size() const { return c.max_size (); } + + std::pair<iterator,bool>insert(const value_type& x) { return c.insert_one (x); } + + void erase(iterator position) { c.erase (position); } + size_type erase(const key_type& x) { return c.erase_one (x); } + void erase(iterator first, iterator last){ c.erase (first, last); } + void clear() { c.clear (); } + + void swap(vector_set& x) { c.swap (x.c); } + + // set operations: + iterator find(const key_type& x) { return c.find (x); } + const_iterator find(const key_type& x) const { return c.find (x); } + size_type count(const key_type& x) const { return c.count_one (x); } + + iterator lower_bound(const key_type& x) { return c.lower_bound (x); } + const_iterator lower_bound(const key_type& x) const{ return c.lower_bound (x); } + + iterator upper_bound(const key_type& x) { return c.upper_bound (x); } + const_iterator upper_bound(const key_type& x) const{ return c.upper_bound (x); } + + std::pair<iterator,iterator> equal_range(const key_type& x) { return c.equal_range (x); } + std::pair<const_iterator, const_iterator> equal_range(const key_type& x) const { return c.equal_range (x); } + + // vector specific operations + void reserve (size_type n) { c.reserve (n); } + + value_type& operator [] (int n) { return c[n]; } + const value_type& operator [] (int n) const { return c[n]; } + + vector_container& get_vector () { return c.c; } + + void push_unsorted (const value_type& x) + { + get_vector().push_back(x); + } + + void sort () + { + std::sort(c.c.begin(), c.c.end(), c.value_comp ()); + c.verify_duplicates_and_sorted (); + } + + void verify_duplicates_and_sorted () const + { + c.verify_duplicates_and_sorted (); + } + + + public: + + container c; +}; + +template <class Key, class Compare = std::equal_to<Key>, class Allocator = std::allocator<Key> > +class us_vector_set +{ + public: + + typedef unsorted_vector<Key, Compare, Allocator> container; + typedef typename container::container vector_container; + typedef typename container::iterator iterator; + typedef typename container::const_iterator const_iterator; + typedef typename container::reverse_iterator reverse_iterator; + typedef typename container::const_reverse_iterator const_reverse_iterator; + typedef typename container::value_type value_type; + typedef typename container::value_type key_type; + typedef Compare key_compare; + typedef Compare value_compare; + typedef typename container::size_type size_type; + typedef typename container::difference_type difference_type; + typedef Allocator allocator_type; + + + us_vector_set (const Compare& comp = Compare (), const Allocator& a = Allocator()) + : c (comp, a) { } + + template <class InputIterator> + us_vector_set (InputIterator first, InputIterator last, const Compare& comp = Compare (), const Allocator& a = Allocator()) + : c (comp, a) { insert_one (first, last); } + + bool empty () const { return c.empty (); } + + iterator begin() { return c.begin (); } + const_iterator begin() const { return c.begin (); } + iterator end() { return c.end (); } + const_iterator end() const { return c.end (); } + reverse_iterator rbegin() { return c.rbegin (); } + const_reverse_iterator rbegin() const { return c.rbegin (); } + reverse_iterator rend() { return c.rend (); } + const_reverse_iterator rend() const { return c.rend (); } + + size_type size() const { return c.size (); } + size_type max_size() const { return c.max_size (); } + + std::pair<iterator,bool> insert(const value_type& x) { return c.insert_one (x); } + + void erase(iterator position) { c.erase (position); } + size_type erase(const key_type& x) { return c.erase_one (x); } + void erase(iterator first, iterator last){ c.erase (first, last); } + void clear() { c.clear (); } + + void swap(us_vector_set& x) { c.swap (x.c); } + + // set operations: + iterator find(const key_type& x) { return c.find (x); } + const_iterator find(const key_type& x) const { return c.find (x); } + size_type count(const key_type& x) const { return c.count_one (x); } + + // vector specific operations + void reserve (size_type n) { c.reserve (n); } + + value_type& operator [] (int n) { return c[n]; } + const value_type& operator [] (int n) const { return c[n]; } + + vector_container& get_vector () { return c.c; } + + public: + + container c; +}; + +#endif diff --git a/Runtime/Utilities/vector_utility.h b/Runtime/Utilities/vector_utility.h new file mode 100644 index 0000000..a632413 --- /dev/null +++ b/Runtime/Utilities/vector_utility.h @@ -0,0 +1,95 @@ +#ifndef VECTOR_UTILITY_H +#define VECTOR_UTILITY_H + +// stl vector doesnt deallocate memory on itself. +// push_back and resizing increases memory by a factor of 2, when growing +// pop_back, resizing and even clear do not deallocate any memory +// This of course wastes a lot of memory, fortunately there is the swap trick to +// bring back memory usage to normality +//#include <algorithm> + +template<class T> +inline void trim_vector (T& v) +{ + if (v.capacity () != v.size ()) + { + T temp = v; + temp.swap (v); + } +} + +// The following functions do the same as their vector member function +// equivalents, only they allocate exactly the requested amount of memory. + +template<class T> +inline void resize_trimmed (T& v, unsigned int sz) +{ + // the vector is growing + if (sz > v.size ()) + { + if (sz != v.capacity ()) + { + T temp (v.get_allocator()); + temp.reserve (sz); + temp.assign (v.begin (), v.end ()); + temp.resize (sz); + temp.swap (v); + } + else + v.resize (sz); + } + // the vector is shrinking + else if (sz < v.size ()) + { + T temp (v.begin (), v.begin () + sz, v.get_allocator()); + temp.swap (v); + } +} + +template<class T> +inline void reserve_trimmed (T& v, unsigned int sz) +{ + if (sz != v.capacity ()) + { + T temp; + temp.reserve (sz); + temp.assign (v.begin (), v.end ()); + temp.swap (v); + } +} + +template<class T> +inline void clear_trimmed (T& v) +{ + T temp; + temp.swap (v); +} + +template<class T> +inline void push_back_trimmed (T& vec, const typename T::value_type& value) +{ + if (vec.size () + 1 != vec.capacity ()) + { + T temp; + temp.reserve (vec.size () + 1); + temp.assign (vec.begin (), vec.end ()); + temp.push_back (value); + temp.swap (vec); + } + else + vec.push_back (value); +} + +template<class T> +inline void erase_trimmed (T& vec, typename T::iterator i) +{ + AssertIf (i == vec.end ()); + + T temp; + temp.reserve (vec.size () - 1); + temp.assign (vec.begin (), i); + temp.insert (temp.end (), ++i, vec.end ()); + vec.swap (temp); +} + +#endif |