forked from aya/aya
Initial commit
This commit is contained in:
103
engine/core/CMakeLists.txt
Normal file
103
engine/core/CMakeLists.txt
Normal file
@@ -0,0 +1,103 @@
|
||||
add_library(Core STATIC)
|
||||
|
||||
set(SOURCES
|
||||
src/AyaAssert.cpp
|
||||
src/AyaAssert.hpp
|
||||
src/AyaBase.hpp
|
||||
src/AyaCrash.cpp
|
||||
src/AyaDbgInfo.cpp
|
||||
src/AyaDbgInfo.hpp
|
||||
src/AyaFormat.cpp
|
||||
src/AyaFormat.hpp
|
||||
src/AyaPlatform.hpp
|
||||
src/AyaStrings.hpp
|
||||
src/BaldPtr.hpp
|
||||
src/CEvent.cpp
|
||||
src/CEvent.hpp
|
||||
src/CPUCount.cpp
|
||||
src/CPUCount.hpp
|
||||
src/Coordinator.cpp
|
||||
src/Coordinator.hpp
|
||||
src/Crypt.cpp
|
||||
src/Crypt.hpp
|
||||
src/Debug.cpp
|
||||
src/Debug.hpp
|
||||
src/Declarations.hpp
|
||||
src/DenseHash.hpp
|
||||
src/FastLog.cpp
|
||||
src/FastLog.hpp
|
||||
src/FastLogStream.cpp
|
||||
src/FastLogStream.hpp
|
||||
src/GlobalVectorItem.hpp
|
||||
src/HardwareInfo.hpp
|
||||
src/ImGui.cpp
|
||||
src/ImGui.hpp
|
||||
src/Log.cpp
|
||||
src/Log.hpp
|
||||
src/MathUtil.cpp
|
||||
src/MathUtil.hpp
|
||||
src/Memory.cpp
|
||||
src/Memory.hpp
|
||||
src/Nil.hpp
|
||||
src/ProcessPerfCounter.cpp
|
||||
src/ProcessPerfCounter.hpp
|
||||
src/Profiler.cpp
|
||||
src/Profiler.hpp
|
||||
src/RegistryUtil.hpp
|
||||
src/RunningAverage.hpp
|
||||
src/SafeToLower.hpp
|
||||
src/ScopedSingleton.hpp
|
||||
src/SelectState.hpp
|
||||
src/SimpleJSON.cpp
|
||||
src/SimpleJSON.hpp
|
||||
src/StreamHelpers.cpp
|
||||
src/StringConv.cpp
|
||||
src/StringConv.hpp
|
||||
src/SystemUtil.cpp
|
||||
src/SystemUtil.hpp
|
||||
src/TaskScheduler.Job.cpp
|
||||
src/TaskScheduler.Job.hpp
|
||||
src/TaskScheduler.Thread.cpp
|
||||
src/TaskScheduler.cpp
|
||||
src/TaskScheduler.hpp
|
||||
src/ThreadSafe.cpp
|
||||
src/atomic.hpp
|
||||
src/boost.cpp
|
||||
src/callable.hpp
|
||||
src/format_string.cpp
|
||||
src/format_string.hpp
|
||||
src/intrusive_ptr_target.hpp
|
||||
src/intrusive_set.hpp
|
||||
src/intrusive_weak_ptr.hpp
|
||||
src/make_shared.hpp
|
||||
src/object_pool.hpp
|
||||
src/signal.cpp
|
||||
src/signal.hpp
|
||||
src/stringbuffer.hpp
|
||||
src/threadsafe.hpp
|
||||
src/time.cpp
|
||||
src/time.hpp
|
||||
src/trie.hpp
|
||||
src/ublas_ext.hpp
|
||||
)
|
||||
|
||||
# Platform-specific configuration
|
||||
if(AYA_OS_ANDROID)
|
||||
list(APPEND SOURCES
|
||||
src/ifaddrs.cpp
|
||||
src/ifaddrs.hpp
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(Core
|
||||
PUBLIC
|
||||
${ENGINE_DIR}/core/src
|
||||
PRIVATE
|
||||
${THIRD_PARTY_DIR}/BulletPhysics/src
|
||||
${ENGINE_DIR}/app/src
|
||||
${ENGINE_DIR}/3d/src
|
||||
${ENGINE_DIR}/gfx/src
|
||||
${ENGINE_DIR}/gfx/src/API/GL/glad/include
|
||||
)
|
||||
|
||||
target_sources(Core PRIVATE ${SOURCES})
|
||||
810
engine/core/src/ArrayDynamic.hpp
Normal file
810
engine/core/src/ArrayDynamic.hpp
Normal file
@@ -0,0 +1,810 @@
|
||||
#pragma once
|
||||
|
||||
#include "Debug.hpp"
|
||||
#include "boost/utility.hpp"
|
||||
#include "boost/type_traits.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
//
|
||||
// ArrayBase
|
||||
//
|
||||
template<class T>
|
||||
class ArrayBase
|
||||
{
|
||||
public:
|
||||
typedef T value_type;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T* iterator;
|
||||
typedef const T* const_iterator;
|
||||
typedef T* reverse_iterator;
|
||||
typedef const T* const_reverse_iterator;
|
||||
typedef ptrdiff_t difference_type;
|
||||
typedef size_t size_type;
|
||||
|
||||
inline T& operator[](size_t i);
|
||||
inline const T& operator[](size_t i) const;
|
||||
inline T& at(size_t i);
|
||||
inline const T& at(size_t i) const;
|
||||
inline size_t size() const;
|
||||
inline T& front();
|
||||
inline const T& front() const;
|
||||
inline T& back();
|
||||
inline const T& back() const;
|
||||
inline T* begin();
|
||||
inline const T* begin() const;
|
||||
inline const T* cbegin() const;
|
||||
inline T* end();
|
||||
inline const T* end() const;
|
||||
inline const T* cend() const;
|
||||
inline T* data();
|
||||
inline const T* data() const;
|
||||
inline const T* cdata() const;
|
||||
inline bool empty() const;
|
||||
|
||||
protected:
|
||||
inline ArrayBase(T* _data, size_t _size);
|
||||
inline ArrayBase(const ArrayBase<T>& a);
|
||||
|
||||
private:
|
||||
inline ArrayBase<T>& operator=(const ArrayBase<T>& src);
|
||||
|
||||
protected:
|
||||
T* mData;
|
||||
size_t mSize;
|
||||
};
|
||||
|
||||
//
|
||||
// ArrayBase Implementation
|
||||
//
|
||||
template<class T>
|
||||
ArrayBase<T>::ArrayBase(T* _data, size_t _size)
|
||||
: mData(_data)
|
||||
, mSize(_size)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayBase<T>::ArrayBase(const ArrayBase<T>& a)
|
||||
: mData(a.mData)
|
||||
, mSize(a.mSize)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T& ArrayBase<T>::operator[](size_t i)
|
||||
{
|
||||
AYAASSERT_VERY_FAST(i < mSize);
|
||||
return mData[i];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T& ArrayBase<T>::operator[](size_t i) const
|
||||
{
|
||||
AYAASSERT_VERY_FAST(i < mSize);
|
||||
return mData[i];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T& ArrayBase<T>::at(size_t i)
|
||||
{
|
||||
AYAASSERT_VERY_FAST(i < mSize);
|
||||
return mData[i];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T& ArrayBase<T>::at(size_t i) const
|
||||
{
|
||||
AYAASSERT_VERY_FAST(i < mSize);
|
||||
return mData[i];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline size_t ArrayBase<T>::size() const
|
||||
{
|
||||
return mSize;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T& ArrayBase<T>::front()
|
||||
{
|
||||
AYAASSERT_VERY_FAST(mSize > 0);
|
||||
return mData[0];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T& ArrayBase<T>::front() const
|
||||
{
|
||||
AYAASSERT_VERY_FAST(mSize > 0);
|
||||
return mData[0];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T& ArrayBase<T>::back()
|
||||
{
|
||||
AYAASSERT_VERY_FAST(mSize > 0);
|
||||
return mData[mSize - 1];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T& ArrayBase<T>::back() const
|
||||
{
|
||||
AYAASSERT_VERY_FAST(mSize > 0);
|
||||
return mData[mSize - 1];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T* ArrayBase<T>::begin()
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T* ArrayBase<T>::begin() const
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T* ArrayBase<T>::cbegin() const
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T* ArrayBase<T>::end()
|
||||
{
|
||||
return mData + mSize;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T* ArrayBase<T>::end() const
|
||||
{
|
||||
return mData + mSize;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T* ArrayBase<T>::cend() const
|
||||
{
|
||||
return mData + mSize;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T* ArrayBase<T>::data()
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T* ArrayBase<T>::data() const
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline const T* ArrayBase<T>::cdata() const
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline bool ArrayBase<T>::empty() const
|
||||
{
|
||||
return mSize == 0;
|
||||
}
|
||||
|
||||
struct ArrayNoInit
|
||||
{
|
||||
};
|
||||
|
||||
//
|
||||
// ArrayDynamic
|
||||
//
|
||||
template<class T>
|
||||
class ArrayDynamic : public ArrayBase<T>
|
||||
{
|
||||
public:
|
||||
static const boost::uint32_t defaultAlignment = 16;
|
||||
typedef ArrayBase<T> Base;
|
||||
inline ArrayDynamic();
|
||||
inline explicit ArrayDynamic(size_t _size, ArrayNoInit, boost::uint32_t _align = defaultAlignment);
|
||||
inline explicit ArrayDynamic(size_t _size, boost::uint32_t _align = defaultAlignment);
|
||||
inline ArrayDynamic(const ArrayDynamic<T>& a);
|
||||
inline ArrayDynamic(const ArrayBase<T>& a);
|
||||
inline ~ArrayDynamic();
|
||||
inline void clear();
|
||||
inline ArrayDynamic<T>& operator=(const ArrayDynamic<T>& _a);
|
||||
inline ArrayDynamic<T>& operator=(const ArrayBase<T>& _a);
|
||||
inline void reserve(size_t _capacity);
|
||||
inline void resize(size_t _size);
|
||||
inline size_t capacity() const;
|
||||
inline void push_back(const T& _a);
|
||||
inline void pop_back();
|
||||
inline void insert(size_t i, const T& val);
|
||||
inline T* insert(const T* it, const T& val);
|
||||
template<class InputType>
|
||||
inline void insert_count(T* it, InputType first, size_t count);
|
||||
template<class InputType>
|
||||
inline void insert(T* it, InputType first, InputType last);
|
||||
void assign(size_t size, const T& value);
|
||||
|
||||
private:
|
||||
template<class InputType>
|
||||
inline void copyConstruct(void* dst, InputType src, size_t count);
|
||||
inline void copyConstruct(void* dst, const T* src, size_t count);
|
||||
inline void increase_capacity(size_t requestedCapacity);
|
||||
|
||||
size_t mCapacity;
|
||||
bool mNoInit;
|
||||
boost::uint32_t mAlignment;
|
||||
};
|
||||
|
||||
//
|
||||
// ArrayDynamic implementation
|
||||
//
|
||||
|
||||
namespace array_dynamic_details
|
||||
{
|
||||
inline void* aligned_alloc(std::size_t alignment, std::size_t size) BOOST_NOEXCEPT
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (alignment < sizeof(void*))
|
||||
{
|
||||
alignment = sizeof(void*);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
void* p = _aligned_malloc(size, alignment);
|
||||
#elif defined(__ANDROID__)
|
||||
void* p = ::memalign(alignment, size);
|
||||
#else
|
||||
void* p;
|
||||
if (::posix_memalign(&p, alignment, size) != 0)
|
||||
{
|
||||
p = 0;
|
||||
}
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
inline void aligned_free(void* ptr) BOOST_NOEXCEPT
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
::free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Construct
|
||||
//
|
||||
template<class T>
|
||||
static void construct(void* dst, size_t count, const boost::true_type& hasTrivialConstructor)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void construct(void* dst, size_t count, const boost::false_type& hasTrivialConstructor)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
new (reinterpret_cast<char*>(dst) + i * sizeof(T)) T();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void construct(void* dst, size_t count)
|
||||
{
|
||||
construct<T>(dst, count, boost::has_trivial_constructor<T>());
|
||||
}
|
||||
|
||||
//
|
||||
// Copy
|
||||
//
|
||||
template<class T>
|
||||
static void copyTrivial(void* dst, const T* src, size_t count, const boost::false_type& isFundamentalOrPointer)
|
||||
{
|
||||
memcpy(dst, src, count * sizeof(T));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void copyTrivial(void* dst, const T* src, size_t count, const boost::true_type& isFundamentalOrPointer)
|
||||
{
|
||||
if (count > 16)
|
||||
{
|
||||
copyTrivial(dst, src, count, boost::false_type());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
new (reinterpret_cast<char*>(dst) + i * sizeof(T)) T(src[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<bool B>
|
||||
struct bool_type : boost::integral_constant<bool, B>
|
||||
{
|
||||
static const bool value = B;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
static void copyTrivial(void* dst, const T* src, size_t count)
|
||||
{
|
||||
copyTrivial(dst, src, count, bool_type<boost::is_fundamental<T>::value || boost::is_pointer<T>::value>());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void copyConstruct(void* dst, const T* src, size_t count, const boost::true_type& hasTrivialCopyConstruct)
|
||||
{
|
||||
copyTrivial(dst, src, count);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void copyConstruct(void* dst, const T* src, size_t count, const boost::false_type& hasTrivialCopyConstruct)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
new (reinterpret_cast<char*>(dst) + i * sizeof(T)) T(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void copyConstruct(void* dst, const T* src, size_t count)
|
||||
{
|
||||
copyConstruct(dst, src, count, boost::has_trivial_copy_constructor<T>());
|
||||
}
|
||||
|
||||
//
|
||||
// Destroy
|
||||
//
|
||||
template<class T>
|
||||
void destroy(T* src, size_t count, const boost::false_type& hasTrivialDestructor)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
src[i].~T();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void destroy(T* src, size_t count, const boost::true_type& hasTrivialDestructor)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void destroy(T* src, size_t count)
|
||||
{
|
||||
destroy(src, count, boost::has_trivial_destructor<T>());
|
||||
}
|
||||
|
||||
//
|
||||
// Shift right
|
||||
//
|
||||
template<class T>
|
||||
static void shiftRightTrivialCopy(T* src, size_t count, size_t offset, const boost::false_type& isFundamentalOrPointer)
|
||||
{
|
||||
memmove(static_cast<void*>(src + offset), src, count * sizeof(T));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void shiftRightTrivialCopy(T* src, size_t count, size_t offset, const boost::true_type& isFundamentalOrPointer)
|
||||
{
|
||||
if (count < 16)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
new ((T*)src + count + offset - 1 - i) T(src[count - 1 - i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shiftRightTrivialCopy(src, count, offset, boost::false_type());
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void shiftRightTrivialCopy(T* src, size_t count, size_t offset)
|
||||
{
|
||||
shiftRightTrivialCopy(src, count, offset, boost::integral_constant<bool, boost::is_fundamental<T>::value || boost::is_pointer<T>::value>());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void shiftRight(T* src, size_t count, size_t offset, const boost::true_type& hasTrivialCopy)
|
||||
{
|
||||
shiftRightTrivialCopy(src, count, offset, boost::integral_constant<bool, boost::is_fundamental<T>::value || boost::is_pointer<T>::value>());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void shiftRightNonTrivialOverlapping(T* src, size_t count, size_t offset, const boost::false_type& hasTrivialDestructor)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
new ((T*)src + count + offset - 1 - i) T(src[count - 1 - i]);
|
||||
src[count - 1 - i].~T();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void shiftRightNonTrivialOverlapping(T* src, size_t count, size_t offset, const boost::true_type& hasTrivialDestructor)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
new ((T*)src + count + offset - 1 - i) T(src[count - 1 - i]);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void shiftRight(T* src, size_t count, size_t offset, const boost::false_type& hasTrivialCopy)
|
||||
{
|
||||
shiftRightNonTrivialOverlapping(src, count, offset, boost::has_trivial_destructor<T>());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void shiftRight(T* src, size_t count, size_t offset)
|
||||
{
|
||||
shiftRight(src, count, offset, boost::has_trivial_copy<T>());
|
||||
}
|
||||
} // namespace array_dynamic_details
|
||||
|
||||
template<class T>
|
||||
template<class InputType>
|
||||
void ArrayDynamic<T>::copyConstruct(void* dst, InputType src, size_t count)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
new (reinterpret_cast<char*>(dst) + i * sizeof(T)) T(*src);
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ArrayDynamic<T>::copyConstruct(void* dst, const T* src, size_t count)
|
||||
{
|
||||
if (mNoInit)
|
||||
{
|
||||
array_dynamic_details::copyTrivial(dst, src, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
array_dynamic_details::copyConstruct(dst, src, count);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>::ArrayDynamic()
|
||||
: ArrayBase<T>(NULL, 0)
|
||||
, mAlignment(16)
|
||||
, mNoInit(false)
|
||||
, mCapacity(0)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>::ArrayDynamic(size_t _size, boost::uint32_t _align)
|
||||
: ArrayBase<T>(NULL, 0)
|
||||
, mAlignment(_align)
|
||||
, mNoInit(false)
|
||||
, mCapacity(0)
|
||||
{
|
||||
reserve(_size);
|
||||
Base::mSize = _size;
|
||||
|
||||
// Initialize
|
||||
array_dynamic_details::construct<T>(Base::mData, Base::mSize);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>::ArrayDynamic(size_t _size, ArrayNoInit, boost::uint32_t _align)
|
||||
: ArrayBase<T>(NULL, 0)
|
||||
, mAlignment(_align)
|
||||
, mNoInit(true)
|
||||
, mCapacity(0)
|
||||
{
|
||||
reserve(_size);
|
||||
Base::mSize = _size;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>::ArrayDynamic(const ArrayDynamic<T>& a)
|
||||
: ArrayBase<T>(NULL, 0)
|
||||
, mAlignment(a.mAlignment)
|
||||
, mNoInit(a.mNoInit)
|
||||
, mCapacity(0)
|
||||
{
|
||||
reserve(a.size());
|
||||
copyConstruct(Base::mData, a.data(), a.size());
|
||||
Base::mSize = a.size();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>::ArrayDynamic(const ArrayBase<T>& a)
|
||||
: ArrayBase<T>(NULL, 0)
|
||||
, mAlignment(defaultAlignment)
|
||||
, mNoInit(false)
|
||||
, mCapacity(0)
|
||||
{
|
||||
reserve(a.size());
|
||||
copyConstruct(Base::mData, a.data(), a.size());
|
||||
Base::mSize = a.size();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>::~ArrayDynamic()
|
||||
{
|
||||
clear();
|
||||
if (mCapacity > 0)
|
||||
{
|
||||
array_dynamic_details::aligned_free(Base::mData);
|
||||
mCapacity = 0;
|
||||
Base::mData = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ArrayDynamic<T>::clear()
|
||||
{
|
||||
if (!mNoInit)
|
||||
{
|
||||
array_dynamic_details::destroy(Base::mData, Base::mSize);
|
||||
}
|
||||
Base::mSize = 0;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>& ArrayDynamic<T>::operator=(const ArrayDynamic<T>& _a)
|
||||
{
|
||||
clear();
|
||||
if (mCapacity > 0 && mAlignment != _a.mAlignment)
|
||||
{
|
||||
array_dynamic_details::aligned_free(Base::mData);
|
||||
mCapacity = 0;
|
||||
Base::mData = NULL;
|
||||
}
|
||||
mNoInit = _a.mNoInit;
|
||||
mAlignment = _a.mAlignment;
|
||||
reserve(_a.size());
|
||||
copyConstruct(Base::mData, _a.data(), _a.size());
|
||||
Base::mSize = _a.size();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayDynamic<T>& ArrayDynamic<T>::operator=(const ArrayBase<T>& _a)
|
||||
{
|
||||
clear();
|
||||
reserve(_a.size());
|
||||
copyConstruct(Base::mData, _a.data(), _a.size());
|
||||
Base::mSize = _a.size();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ArrayDynamic<T>::reserve(size_t _capacity)
|
||||
{
|
||||
if (_capacity > mCapacity)
|
||||
{
|
||||
void* newData = array_dynamic_details::aligned_alloc(mAlignment, _capacity * sizeof(T));
|
||||
if (mNoInit)
|
||||
{
|
||||
array_dynamic_details::copyTrivial(newData, Base::mData, Base::mSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
array_dynamic_details::copyConstruct(newData, Base::mData, Base::mSize);
|
||||
array_dynamic_details::destroy(Base::mData, Base::mSize);
|
||||
}
|
||||
|
||||
if (mCapacity > 0)
|
||||
{
|
||||
array_dynamic_details::aligned_free(Base::mData);
|
||||
}
|
||||
Base::mData = (T*)newData;
|
||||
mCapacity = _capacity;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ArrayDynamic<T>::resize(size_t _size)
|
||||
{
|
||||
if (_size <= Base::mSize)
|
||||
{
|
||||
if (!mNoInit)
|
||||
{
|
||||
array_dynamic_details::destroy(Base::mData + _size, Base::mSize - _size);
|
||||
}
|
||||
Base::mSize = _size;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_size > mCapacity)
|
||||
{
|
||||
reserve(_size);
|
||||
}
|
||||
|
||||
if (!mNoInit)
|
||||
{
|
||||
array_dynamic_details::construct<T>(Base::mData + Base::mSize, _size - Base::mSize);
|
||||
}
|
||||
Base::mSize = _size;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline size_t ArrayDynamic<T>::capacity() const
|
||||
{
|
||||
return mCapacity;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void ArrayDynamic<T>::increase_capacity(size_t requestedCapacity)
|
||||
{
|
||||
size_t newCapacity = mCapacity == 0 ? 2 : 2 * mCapacity;
|
||||
while (newCapacity < requestedCapacity)
|
||||
newCapacity *= 2;
|
||||
reserve(newCapacity);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void ArrayDynamic<T>::push_back(const T& _a)
|
||||
{
|
||||
if (mCapacity == Base::mSize)
|
||||
{
|
||||
increase_capacity(mCapacity + 1);
|
||||
}
|
||||
new (Base::data() + Base::mSize) T(_a);
|
||||
Base::mSize++;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void ArrayDynamic<T>::pop_back()
|
||||
{
|
||||
AYAASSERT_VERY_FAST(Base::mSize > 0);
|
||||
Base::mSize--;
|
||||
if (mNoInit)
|
||||
{
|
||||
(Base::data() + Base::mSize)->~T();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ArrayDynamic<T>::insert(size_t i, const T& val)
|
||||
{
|
||||
AYAASSERT_VERY_FAST(i <= Base::mSize);
|
||||
if (i == Base::mSize)
|
||||
{
|
||||
push_back(val);
|
||||
return;
|
||||
}
|
||||
if (mCapacity == Base::mSize)
|
||||
{
|
||||
increase_capacity(mCapacity + 1);
|
||||
}
|
||||
if (mNoInit)
|
||||
{
|
||||
array_dynamic_details::shiftRightTrivialCopy(Base::data() + i, Base::mSize - i, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
array_dynamic_details::shiftRight(Base::data() + i, Base::mSize - i, 1);
|
||||
}
|
||||
Base::mSize++;
|
||||
new (Base::data() + i) T(val);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T* ArrayDynamic<T>::insert(const T* it, const T& val)
|
||||
{
|
||||
AYAASSERT_VERY_FAST(Base::begin() <= it);
|
||||
AYAASSERT_VERY_FAST(Base::end() >= it);
|
||||
size_t index = it - Base::begin();
|
||||
insert(index, val);
|
||||
return Base::begin() + index;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template<class InputType>
|
||||
void ArrayDynamic<T>::insert_count(T* it, InputType first, size_t count)
|
||||
{
|
||||
AYAASSERT_VERY_FAST(Base::begin() <= it);
|
||||
AYAASSERT_VERY_FAST(Base::end() >= it);
|
||||
size_t index = it - Base::begin();
|
||||
if (mCapacity < Base::mSize + count)
|
||||
{
|
||||
increase_capacity(Base::mSize + count);
|
||||
}
|
||||
it = Base::begin() + index;
|
||||
if (it < Base::end())
|
||||
{
|
||||
if (mNoInit)
|
||||
{
|
||||
array_dynamic_details::shiftRightTrivialCopy(it, Base::end() - it, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
array_dynamic_details::shiftRight(it, Base::end() - it, count);
|
||||
}
|
||||
}
|
||||
copyConstruct(it, first, count);
|
||||
Base::mSize += count;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template<class InputType>
|
||||
inline void ArrayDynamic<T>::insert(T* it, InputType first, InputType last)
|
||||
{
|
||||
size_t count = last - first;
|
||||
insert_count(it, first, count);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ArrayDynamic<T>::assign(size_t size, const T& value)
|
||||
{
|
||||
clear();
|
||||
reserve(size);
|
||||
T* it = Base::begin();
|
||||
for (size_t i = 0; i < size; i++)
|
||||
{
|
||||
new (it + i) T(value);
|
||||
}
|
||||
Base::mSize = size;
|
||||
}
|
||||
|
||||
//
|
||||
// ArrayRef
|
||||
//
|
||||
template<class T>
|
||||
class ArrayRef : public ArrayBase<T>
|
||||
{
|
||||
public:
|
||||
typedef ArrayBase<T> Base;
|
||||
|
||||
inline ArrayRef(T* _data, size_t _size);
|
||||
inline ArrayRef(const ArrayRef<T>& a);
|
||||
inline ArrayRef(const ArrayBase<T>& a);
|
||||
inline ArrayRef<T>& operator=(const ArrayBase<T>& src);
|
||||
};
|
||||
|
||||
//
|
||||
// ArrayRef Implementation
|
||||
//
|
||||
template<class T>
|
||||
ArrayRef<T>::ArrayRef(T* _data, size_t _size)
|
||||
: Base(_data, _size)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayRef<T>::ArrayRef(const ArrayRef<T>& a)
|
||||
: Base(a)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayRef<T>::ArrayRef(const ArrayBase<T>& a)
|
||||
: Base(a)
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ArrayRef<T>& ArrayRef<T>::operator=(const ArrayBase<T>& src)
|
||||
{
|
||||
Base::mData = src.mData;
|
||||
Base::mSize = src.mSize;
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
50
engine/core/src/AyaAssert.cpp
Normal file
50
engine/core/src/AyaAssert.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
@file debugAssert.cpp
|
||||
|
||||
Windows implementation of assertion routines.
|
||||
|
||||
@maintainer Morgan McGuire, graphics3d.com
|
||||
|
||||
@created 2001-08-26
|
||||
@edited 2006-02-02
|
||||
*/
|
||||
|
||||
#include "AyaPlatform.hpp" // includes <Windows.h>
|
||||
|
||||
#include "AyaAssert.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace _internal
|
||||
{
|
||||
AssertionHook _debugHook;
|
||||
AssertionHook _failureHook;
|
||||
} // namespace _internal
|
||||
|
||||
void setAssertionHook(AssertionHook hook)
|
||||
{
|
||||
Aya::_internal::_debugHook = hook;
|
||||
}
|
||||
|
||||
AssertionHook assertionHook()
|
||||
{
|
||||
return Aya::_internal::_debugHook;
|
||||
}
|
||||
|
||||
void setFailureHook(AssertionHook hook)
|
||||
{
|
||||
Aya::_internal::_failureHook = hook;
|
||||
}
|
||||
|
||||
AssertionHook failureHook()
|
||||
{
|
||||
return Aya::_internal::_failureHook;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Aya
|
||||
62
engine/core/src/AyaAssert.hpp
Normal file
62
engine/core/src/AyaAssert.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
@file debugAssert.h
|
||||
|
||||
debugAssert(expression);
|
||||
debugAssertM(expression, message);
|
||||
|
||||
@cite
|
||||
John Robbins, Microsoft Systems Journal Bugslayer Column, Feb 1999.
|
||||
<A HREF="http://msdn.microsoft.com/library/periodic/period99/feb99_BUGSLAYE_BUGSLAYE.htm">
|
||||
http://msdn.microsoft.com/library/periodic/period99/feb99_BUGSLAYE_BUGSLAYE.htm</A>
|
||||
|
||||
@cite
|
||||
Douglas Cox, An assert() Replacement, Code of The Day, flipcode, Sept 19, 2000
|
||||
<A HREF="http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-AssertReplace&forum=cotd&id=-1">
|
||||
http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-AssertReplace&forum=cotd&id=-1</A>
|
||||
|
||||
@maintainer Morgan McGuire, matrix@graphics3d.com
|
||||
|
||||
@created 2001-08-26
|
||||
@edited 2006-01-12
|
||||
|
||||
Copyright 2000-2006, Morgan McGuire.
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef x68BFA40003704acb85BC500AEC18DCA7
|
||||
#define x68BFA40003704acb85BC500AEC18DCA7
|
||||
|
||||
#include <string>
|
||||
#include "AyaBase.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
typedef bool (*AssertionHook)(const char* _expression, const char* filename, int lineNumber);
|
||||
|
||||
|
||||
/**
|
||||
Allows customization of the global function invoked when a debugAssert fails.
|
||||
The initial value is Aya::_internal::_handleDebugAssert_. Aya will invoke
|
||||
rawBreak if the hook returns true. If NULL, assertions are not handled.
|
||||
*/
|
||||
void setAssertionHook(AssertionHook hook);
|
||||
|
||||
AssertionHook assertionHook();
|
||||
|
||||
/**
|
||||
Called by alwaysAssertM in case of failure in release mode. If returns
|
||||
true then the program exits with -1 (you can replace this with your own
|
||||
version that throws an exception or has other failure modes).
|
||||
*/
|
||||
void setFailureHook(AssertionHook hook);
|
||||
AssertionHook failureHook();
|
||||
|
||||
namespace _internal
|
||||
{
|
||||
extern AssertionHook _debugHook;
|
||||
extern AssertionHook _failureHook;
|
||||
} // namespace _internal
|
||||
} // namespace Aya
|
||||
|
||||
#endif
|
||||
16
engine/core/src/AyaBase.hpp
Normal file
16
engine/core/src/AyaBase.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
// This will catch some really nasty bugs:
|
||||
#pragma warning(error : 4702)
|
||||
#endif
|
||||
|
||||
#ifndef _DEBUG
|
||||
|
||||
#if _SECURE_SCL != 0
|
||||
#error Define _SECURE_SCL equal to 0 in release builds!
|
||||
#endif
|
||||
|
||||
#endif
|
||||
18
engine/core/src/AyaCrash.cpp
Normal file
18
engine/core/src/AyaCrash.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* AyaCrash.cpp
|
||||
* MacClient
|
||||
*
|
||||
* Created by elebel on 7/20/10.
|
||||
*/
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
void AYACRASH()
|
||||
{
|
||||
Aya::Debugable::doCrash();
|
||||
}
|
||||
|
||||
void AYACRASH(const char* message)
|
||||
{
|
||||
Aya::Debugable::doCrash(message);
|
||||
}
|
||||
74
engine/core/src/AyaDbgInfo.cpp
Normal file
74
engine/core/src/AyaDbgInfo.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "AyaDbgInfo.hpp"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
AyaDbgInfo AyaDbgInfo::s_instance;
|
||||
|
||||
AyaDbgInfo::AyaDbgInfo()
|
||||
{
|
||||
memset(this, 0, sizeof(AyaDbgInfo));
|
||||
}
|
||||
|
||||
void AyaDbgInfo::AddPlace(long ID)
|
||||
{
|
||||
// Shift all places to upper indices
|
||||
for (int i = PLACE_HISTORY - 1; i > 0; i--)
|
||||
{
|
||||
s_instance.PlaceIDs[i] = s_instance.PlaceIDs[i - 1];
|
||||
}
|
||||
s_instance.PlaceIDs[0] = ID;
|
||||
s_instance.PlaceCounter++;
|
||||
}
|
||||
|
||||
void AyaDbgInfo::RemovePlace(long ID)
|
||||
{
|
||||
s_instance.PlaceCounter--;
|
||||
for (int i = 0; i < PLACE_HISTORY; i++)
|
||||
{
|
||||
if (s_instance.PlaceIDs[i] == ID)
|
||||
{
|
||||
// Shift all places after it to lower indices
|
||||
for (int j = i; j < PLACE_HISTORY - 1; j++)
|
||||
{
|
||||
s_instance.PlaceIDs[j] = s_instance.PlaceIDs[j + 1];
|
||||
}
|
||||
s_instance.PlaceIDs[PLACE_HISTORY - 1] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
void AyaDbgInfo::SetGfxCardName(const char* s)
|
||||
{
|
||||
strncpy(s_instance.GfxCardName, s, DBG_STRING_MAX - 1);
|
||||
s_instance.GfxCardName[DBG_STRING_MAX - 1] = '\0';
|
||||
}
|
||||
|
||||
void AyaDbgInfo::SetGfxCardDriverVersion(const char* s)
|
||||
{
|
||||
strncpy(s_instance.GfxCardDriverVersion, s, DBG_STRING_MAX - 1);
|
||||
s_instance.GfxCardDriverVersion[DBG_STRING_MAX - 1] = '\0';
|
||||
}
|
||||
|
||||
void AyaDbgInfo::SetGfxCardVendor(const char* s)
|
||||
{
|
||||
strncpy(s_instance.GfxCardVendorName, s, DBG_STRING_MAX - 1);
|
||||
s_instance.GfxCardVendorName[DBG_STRING_MAX - 1] = '\0';
|
||||
}
|
||||
|
||||
void AyaDbgInfo::SetCPUName(const char* s)
|
||||
{
|
||||
strncpy(s_instance.CPUName, s, DBG_STRING_MAX - 1);
|
||||
s_instance.CPUName[DBG_STRING_MAX - 1] = '\0';
|
||||
}
|
||||
|
||||
void AyaDbgInfo::SetServerIP(const char* s)
|
||||
{
|
||||
strncpy(s_instance.ServerIP, s, DBG_STRING_MAX - 1);
|
||||
s_instance.ServerIP[DBG_STRING_MAX - 1] = '\0';
|
||||
}
|
||||
#pragma warning(pop)
|
||||
70
engine/core/src/AyaDbgInfo.hpp
Normal file
70
engine/core/src/AyaDbgInfo.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#define PLACE_HISTORY 4
|
||||
#define DBG_STRING_MAX 128
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
// Global struct with the sole purpose of being accessible in minidump
|
||||
|
||||
struct AyaDbgInfo
|
||||
{
|
||||
static AyaDbgInfo s_instance;
|
||||
AyaDbgInfo();
|
||||
|
||||
size_t cbMaterials;
|
||||
size_t cbTextures;
|
||||
size_t cbMeshes;
|
||||
size_t cbEstFreeTextureMem;
|
||||
|
||||
size_t cCommitTotal;
|
||||
size_t cCommitLimit;
|
||||
size_t cPhysicalTotal;
|
||||
size_t cPhysicalAvailable;
|
||||
size_t cbPageSize;
|
||||
size_t cKernelPaged;
|
||||
size_t cKernelNonPaged;
|
||||
size_t cSystemCache;
|
||||
size_t HandleCount;
|
||||
size_t ProcessCount;
|
||||
size_t ThreadCount;
|
||||
|
||||
char GfxCardName[DBG_STRING_MAX];
|
||||
char GfxCardDriverVersion[DBG_STRING_MAX];
|
||||
char GfxCardVendorName[DBG_STRING_MAX];
|
||||
size_t TotalVideoMemory;
|
||||
|
||||
char CPUName[DBG_STRING_MAX];
|
||||
size_t NumCores;
|
||||
|
||||
char AudioDeviceName[DBG_STRING_MAX];
|
||||
char ServerIP[DBG_STRING_MAX];
|
||||
|
||||
// Index 0 is always the last place visited
|
||||
union
|
||||
{
|
||||
long PlaceIDs[PLACE_HISTORY];
|
||||
struct
|
||||
{
|
||||
long Place0, Place1, Place2, Place3;
|
||||
};
|
||||
};
|
||||
long PlaceCounter;
|
||||
long PlayerID;
|
||||
|
||||
|
||||
static void SetGfxCardName(const char* s);
|
||||
static void SetGfxCardDriverVersion(const char* s);
|
||||
static void SetGfxCardVendor(const char* s);
|
||||
static void SetCPUName(const char* s);
|
||||
static void SetServerIP(const char* s);
|
||||
|
||||
|
||||
static void AddPlace(long ID);
|
||||
static void RemovePlace(long ID);
|
||||
};
|
||||
} // namespace Aya
|
||||
109
engine/core/src/AyaFormat.cpp
Normal file
109
engine/core/src/AyaFormat.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "AyaFormat.hpp"
|
||||
#include "AyaPlatform.hpp"
|
||||
#include "FastLog.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include "boost/scoped_array.hpp"
|
||||
|
||||
#define NEWLINE "\r\n"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// disable: "C++ exception handler used"
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4530)
|
||||
#endif // _MSC_VER
|
||||
|
||||
// If your platform does not have vsnprintf, you can find a
|
||||
// implementation at http://www.ijs.si/software/snprintf/
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
std::runtime_error runtime_error(const char* fmt, ...)
|
||||
{
|
||||
va_list argList;
|
||||
va_start(argList, fmt);
|
||||
std::string result = vformat(fmt, argList);
|
||||
va_end(argList);
|
||||
|
||||
return std::runtime_error(result);
|
||||
}
|
||||
|
||||
std::string format(const char* fmt, ...)
|
||||
{
|
||||
va_list argList;
|
||||
va_start(argList, fmt);
|
||||
std::string result = vformat(fmt, argList);
|
||||
va_end(argList);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string vformat(const char* fmt, va_list argPtr)
|
||||
{
|
||||
|
||||
if (!fmt)
|
||||
return "";
|
||||
|
||||
// We draw the line at a 1MB string.
|
||||
const int maxSize = 1000000;
|
||||
|
||||
// If the string is less than 161 characters,
|
||||
// allocate it on the stack because this saves
|
||||
// the malloc/free time.
|
||||
const int stackBufferSize = 161;
|
||||
|
||||
#ifdef _WIN32
|
||||
int actualSize = _vscprintf(fmt, argPtr);
|
||||
if (actualSize < stackBufferSize)
|
||||
{
|
||||
char stackBuffer[stackBufferSize];
|
||||
vsnprintf_s(stackBuffer, stackBufferSize, stackBufferSize, fmt, argPtr);
|
||||
return std::string(stackBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the heap.
|
||||
boost::scoped_array<char> heapBuffer;
|
||||
|
||||
if (actualSize > maxSize)
|
||||
actualSize = maxSize;
|
||||
|
||||
heapBuffer.reset(new char[actualSize + 1]);
|
||||
vsnprintf_s(heapBuffer.get(), actualSize + 1, actualSize, fmt, argPtr);
|
||||
|
||||
std::string result(heapBuffer.get());
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
char stackBuffer[stackBufferSize];
|
||||
int actualSize = vsnprintf(stackBuffer, stackBufferSize, fmt, argPtr);
|
||||
if (actualSize < stackBufferSize)
|
||||
return stackBuffer;
|
||||
|
||||
#if defined(__linux) || defined(__APPLE__)
|
||||
// FIXME: THIS IS BAD
|
||||
// Use the heap.
|
||||
return stackBuffer;
|
||||
#else
|
||||
// Use the heap.
|
||||
if (actualSize > maxSize)
|
||||
actualSize = maxSize;
|
||||
#endif
|
||||
|
||||
boost::scoped_array<char> heapBuffer(new char[actualSize + 1]);
|
||||
vsnprintf(heapBuffer.get(), actualSize + 1, fmt, argPtr);
|
||||
heapBuffer[actualSize] = '\0';
|
||||
|
||||
std::string result(heapBuffer.get());
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
} // namespace Aya
|
||||
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#undef NEWLINE
|
||||
123
engine/core/src/AyaFormat.hpp
Normal file
123
engine/core/src/AyaFormat.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#ifndef RBXFORMAT_H
|
||||
#define RBXFORMAT_H
|
||||
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <cstdarg>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "AyaBase.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <objc/objc.h>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define strcasecmp stricmp
|
||||
|
||||
#else
|
||||
|
||||
#define sprintf_s snprintf
|
||||
#define sscanf_s sscanf
|
||||
|
||||
#ifndef DWORD
|
||||
#define DWORD uint32_t
|
||||
#endif
|
||||
|
||||
#ifndef ARRRAYSIZE
|
||||
#define ARRAYSIZE(x) (sizeof(x) / (sizeof(((x)[0]))))
|
||||
#endif
|
||||
|
||||
#endif // ifndef _WIN32
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define AYA_PRINTF_ATTR(string_index, first_to_check) __attribute__((format(printf, string_index, first_to_check)))
|
||||
#else
|
||||
#define AYA_PRINTF_ATTR(string_index, first_to_check)
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// Convenience function:
|
||||
std::runtime_error runtime_error(const char* fmt, ...) AYA_PRINTF_ATTR(1, 2);
|
||||
|
||||
#ifdef AYA_PLATFORM_IOS
|
||||
typedef std::runtime_error base_exception;
|
||||
|
||||
class physics_receiver_exception : public base_exception
|
||||
{
|
||||
public:
|
||||
physics_receiver_exception(const std::string& m)
|
||||
: base_exception(m)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class network_stream_exception : public base_exception
|
||||
{
|
||||
public:
|
||||
network_stream_exception(const std::string& m)
|
||||
: base_exception(m)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
typedef std::exception base_exception;
|
||||
|
||||
class physics_receiver_exception : public base_exception
|
||||
{
|
||||
const std::string msg;
|
||||
|
||||
public:
|
||||
physics_receiver_exception(const std::string& m)
|
||||
: msg(m)
|
||||
{
|
||||
}
|
||||
~physics_receiver_exception() throw() {}
|
||||
const char* what() const throw()
|
||||
{
|
||||
return msg.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
class network_stream_exception : public base_exception
|
||||
{
|
||||
const std::string msg;
|
||||
|
||||
public:
|
||||
network_stream_exception(const std::string& m)
|
||||
: msg(m)
|
||||
{
|
||||
}
|
||||
~network_stream_exception() throw() {}
|
||||
const char* what() const throw()
|
||||
{
|
||||
return msg.c_str();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
Produces a string from arguments of the style of printf. This avoids
|
||||
problems with buffer overflows when using sprintf and makes it easy
|
||||
to use the result functionally. This function is fast when the resulting
|
||||
string is under 160 characters (not including terminator) and slower
|
||||
when the string is longer.
|
||||
*/
|
||||
|
||||
|
||||
std::string format(const char* fmt...) AYA_PRINTF_ATTR(1, 2);
|
||||
/**
|
||||
Like format, but can be called with the argument list from a ... function.
|
||||
*/
|
||||
std::string vformat(const char* fmt, va_list argPtr);
|
||||
|
||||
std::string trim_trailing_slashes(const std::string& path);
|
||||
|
||||
}; // namespace Aya
|
||||
|
||||
#endif
|
||||
25
engine/core/src/AyaPlatform.hpp
Normal file
25
engine/core/src/AyaPlatform.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
// this file meant to include basic OS .h dependencies.
|
||||
// obviously, it only supports windows at this time.
|
||||
#pragma once
|
||||
|
||||
#ifdef WIN32
|
||||
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
|
||||
#ifndef STRICT
|
||||
#define STRICT 1
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX 1
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#undef NOMINMAX
|
||||
|
||||
#undef _G3D_INTERNAL_HIDE_WINSOCK_
|
||||
#undef _WINSOCKAPI_
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
#endif
|
||||
|
||||
#include "AyaBase.hpp"
|
||||
48
engine/core/src/AyaStrings.hpp
Normal file
48
engine/core/src/AyaStrings.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef Aya_strcasestr
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
/* GCC often has strcasestr(); if not, you can use the following */
|
||||
|
||||
/* borrowed these definitions from Apache */
|
||||
#define ap_tolower(c) (tolower(((unsigned char)(c))))
|
||||
#define ap_toupper(c) (toupper(((unsigned char)(c))))
|
||||
|
||||
static const char* Aya_strcasestr(const char* h, const char* n)
|
||||
{
|
||||
if (!h || !*h || !n || !*n)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
char *a = (char*)h, *e = (char*)n;
|
||||
while (*a && *e)
|
||||
{
|
||||
if (ap_toupper(*a) != ap_toupper(*e))
|
||||
{
|
||||
++h;
|
||||
a = (char*)h;
|
||||
e = (char*)n;
|
||||
}
|
||||
else
|
||||
{
|
||||
++a;
|
||||
++e;
|
||||
}
|
||||
}
|
||||
return (const char*)(*e) ? 0 : h;
|
||||
}
|
||||
static inline const char* Aya_strcasestr(const char* h, char* n)
|
||||
{
|
||||
return Aya_strcasestr(h, static_cast<const char*>(n));
|
||||
}
|
||||
|
||||
static inline const char* Aya_strcasestr(char* h, const char* n)
|
||||
{
|
||||
return Aya_strcasestr(static_cast<const char*>(h), n);
|
||||
}
|
||||
} // namespace Aya
|
||||
#endif // defined Aya_strcasestr
|
||||
185
engine/core/src/BaldPtr.hpp
Normal file
185
engine/core/src/BaldPtr.hpp
Normal file
@@ -0,0 +1,185 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
/**
|
||||
* Wraps raw pointers with a layer of protection in debug builds.
|
||||
* Checks for bad pointers on access.
|
||||
*/
|
||||
template<class T>
|
||||
class BaldPtr
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
* Sets pointer to NULL.
|
||||
*/
|
||||
inline BaldPtr()
|
||||
: mPointer(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Pointer is also validated.
|
||||
*
|
||||
* @param Pointer pointer to wrap, may be NULL
|
||||
*/
|
||||
inline BaldPtr(T* Pointer)
|
||||
: mPointer(Pointer)
|
||||
{
|
||||
validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow copies pointer.
|
||||
* Pointer is validated.
|
||||
*
|
||||
* @param Pointer pointer to copy, may be NULL
|
||||
* @return this pointer
|
||||
*/
|
||||
inline T*& operator=(T* Pointer)
|
||||
{
|
||||
mPointer = Pointer;
|
||||
validate();
|
||||
return mPointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dereference operator.
|
||||
* Checks for NULL and validates.
|
||||
*
|
||||
* @return reference to pointer
|
||||
*/
|
||||
inline T& operator*() const
|
||||
{
|
||||
AYAASSERT_VERY_FAST(mPointer);
|
||||
validate();
|
||||
return *mPointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class pointer cast.
|
||||
* Validates pointer.
|
||||
*
|
||||
* @return pointer cast to a class *, may be NULL
|
||||
*/
|
||||
inline operator T*() const
|
||||
{
|
||||
validate();
|
||||
return mPointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrow operator that checks and returns the pointer.
|
||||
* Checks for NULL and validates.
|
||||
*
|
||||
* @return data pointer
|
||||
*/
|
||||
inline T* operator->() const
|
||||
{
|
||||
AYAASSERT_VERY_FAST(mPointer);
|
||||
validate();
|
||||
return mPointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw pointer value.
|
||||
* Performs no checks.
|
||||
*
|
||||
* @return data pointer, may be NULL
|
||||
*/
|
||||
inline T* get() const
|
||||
{
|
||||
return mPointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the pointer.
|
||||
* Various memory patterns are checked such as fence posts,
|
||||
* deleted, and allocated memory. Standard CRT patterns are
|
||||
* checked as well as Ogre patterns.
|
||||
*
|
||||
* @return description
|
||||
*/
|
||||
inline void validate() const
|
||||
{
|
||||
#ifdef __AYA_VERY_FAST_ASSERT
|
||||
if (!mPointer)
|
||||
return;
|
||||
#endif
|
||||
|
||||
AYAASSERT_VERY_FAST(
|
||||
// CRT debug allocator
|
||||
(unsigned)mPointer != 0xCCCCCCCC && // uninitialized stack memory
|
||||
(unsigned)mPointer != 0xCDCDCDCD && // uninitialized heap memory
|
||||
(unsigned)mPointer != 0xFDFDFDFD && // "no man's land" guard bytes before and after allocated heap memory
|
||||
(unsigned)mPointer != 0xDDDDDDDD && // deleted heap memory
|
||||
(unsigned)mPointer != 0xFEEEFEEE && // deleted heap memory
|
||||
|
||||
// Ogre allocator
|
||||
(unsigned)mPointer != 0xBAADF00D && // before "no man's land" guard bytes
|
||||
(unsigned)mPointer != 0xDEADC0DE && // after "no man's land" guard bytes
|
||||
(unsigned)mPointer != 0xFEEDFACE && // uninitialized memory
|
||||
(unsigned)mPointer != 0xDEADBEEF); // deleted memory
|
||||
}
|
||||
|
||||
private:
|
||||
T* mPointer;
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
// struct Foo
|
||||
// {
|
||||
// int x;
|
||||
// };
|
||||
//
|
||||
// template<typename Ptr>
|
||||
// void test()
|
||||
// {
|
||||
// Ptr f = new Foo();
|
||||
// Foo* f2 = f;
|
||||
// f->x = 23;
|
||||
// (*f).x = 32;
|
||||
// void* v = f;
|
||||
// BaldPtr<Foo> b = f;
|
||||
// BaldPtr<Foo> b2(f);
|
||||
// const void* vc = f;
|
||||
//
|
||||
// delete f;
|
||||
// }
|
||||
//
|
||||
// template<typename Ptr>
|
||||
// void testConst()
|
||||
// {
|
||||
// Ptr f = new Foo();
|
||||
// const Foo* f2 = f;
|
||||
// int x = f->x;
|
||||
// int y = (*f).x;
|
||||
// const void* v = f;
|
||||
// BaldPtr<const Foo> b = f;
|
||||
// BaldPtr<const Foo> b2(f);
|
||||
//
|
||||
// delete f;
|
||||
// }
|
||||
//
|
||||
// int _tmain(int argc, _TCHAR* argv[])
|
||||
// {
|
||||
// test<BaldPtr<Foo> const>();
|
||||
// test<Foo* const>();
|
||||
// test<BaldPtr<Foo> >();
|
||||
// test<Foo* >();
|
||||
//
|
||||
// testConst<BaldPtr<const Foo> const>();
|
||||
// testConst<const Foo* const>();
|
||||
// testConst<BaldPtr<const Foo> >();
|
||||
// testConst<const Foo* >();
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
116
engine/core/src/CEvent.cpp
Normal file
116
engine/core/src/CEvent.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
#include "Debug.hpp"
|
||||
#include "CEvent.hpp"
|
||||
|
||||
#ifndef _WIN32
|
||||
const int Aya::CEvent::cWAIT_OBJECT_0;
|
||||
const int Aya::CEvent::cWAIT_TIMEOUT;
|
||||
const int Aya::CEvent::cINFINITE;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
|
||||
void Aya::CEvent::Wait()
|
||||
{
|
||||
WaitForSingleObject(*this, cINFINITE);
|
||||
}
|
||||
|
||||
bool Aya::CEvent::Wait(int milliseconds)
|
||||
{
|
||||
return WaitForSingleObject(*this, milliseconds) == cWAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
#ifdef AYA_CEVENT_BOOST
|
||||
|
||||
Aya::CEvent::~CEvent() throw() {}
|
||||
|
||||
Aya::CEvent::CEvent(bool bManualReset)
|
||||
: isSet(false)
|
||||
, manualReset(bManualReset)
|
||||
{
|
||||
}
|
||||
|
||||
void Aya::CEvent::Set() throw()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(mut);
|
||||
if (manualReset)
|
||||
{
|
||||
isSet = true;
|
||||
cond.notify_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
isSet = true;
|
||||
cond.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
int Aya::CEvent::WaitForSingleObject(CEvent& event, int milliseconds)
|
||||
{
|
||||
if (milliseconds == cINFINITE)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(event.mut);
|
||||
if (!event.isSet)
|
||||
event.cond.wait(lock);
|
||||
if (!event.manualReset)
|
||||
event.isSet = false;
|
||||
return cWAIT_OBJECT_0;
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::system_time const time = boost::get_system_time() + boost::posix_time::milliseconds(milliseconds);
|
||||
boost::unique_lock<boost::mutex> lock(event.mut);
|
||||
bool result = event.isSet || event.cond.timed_wait(lock, time);
|
||||
if (result && !event.manualReset)
|
||||
event.isSet = false;
|
||||
return result ? cWAIT_OBJECT_0 : cWAIT_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
static void WINAPI RbxThrowLastWin32()
|
||||
{
|
||||
DWORD dwError = ::GetLastError();
|
||||
HRESULT hr = HRESULT_FROM_WIN32(dwError);
|
||||
throw Aya::runtime_error("HRESULT = 0x%.8X", hr);
|
||||
}
|
||||
|
||||
|
||||
Aya::CEvent::~CEvent() throw()
|
||||
{
|
||||
if (m_h != NULL)
|
||||
{
|
||||
BOOL result = ::CloseHandle(m_h);
|
||||
AYAASSERT(result != 0);
|
||||
m_h = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Aya::CEvent::CEvent(bool bManualReset)
|
||||
: m_h(NULL)
|
||||
{
|
||||
m_h = ::CreateEvent(NULL, bManualReset ? TRUE : FALSE, FALSE, NULL);
|
||||
if (!m_h)
|
||||
RbxThrowLastWin32();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Aya::CEvent::Set() throw()
|
||||
{
|
||||
if (m_h && !::SetEvent(m_h))
|
||||
RbxThrowLastWin32();
|
||||
}
|
||||
|
||||
int Aya::CEvent::WaitForSingleObject(CEvent& event, int milliseconds)
|
||||
{
|
||||
return ::WaitForSingleObject(event.m_h, milliseconds);
|
||||
}
|
||||
|
||||
#endif
|
||||
51
engine/core/src/CEvent.hpp
Normal file
51
engine/core/src/CEvent.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
#include "boost/noncopyable.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#include "time.hpp"
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
#ifndef _WIN32
|
||||
#define AYA_CEVENT_BOOST
|
||||
#endif
|
||||
|
||||
// TODO: This class is modeled heavily off of ATL::CEvent and should be
|
||||
// cleaned up. Probably it should be split into 2 classes:
|
||||
// Manual and Automatic
|
||||
class CEvent : public boost::noncopyable
|
||||
{
|
||||
#ifdef AYA_CEVENT_BOOST
|
||||
const bool manualReset;
|
||||
volatile bool isSet;
|
||||
boost::condition_variable cond;
|
||||
boost::mutex mut;
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
private:
|
||||
void* m_h;
|
||||
#endif
|
||||
#endif
|
||||
public:
|
||||
CEvent(bool bManualReset);
|
||||
~CEvent() throw();
|
||||
void Set() throw();
|
||||
void Wait();
|
||||
// TODO: Deprecate:
|
||||
bool Wait(int milliseconds);
|
||||
bool Wait(Aya::Time::Interval interval)
|
||||
{
|
||||
return Wait((int)(1000.0 * interval.seconds()));
|
||||
}
|
||||
|
||||
private:
|
||||
static const int cWAIT_OBJECT_0 = 0;
|
||||
static const int cWAIT_TIMEOUT = 258;
|
||||
static const int cINFINITE = 0xFFFFFFFF;
|
||||
static int WaitForSingleObject(CEvent& event, int milliseconds);
|
||||
};
|
||||
} // namespace Aya
|
||||
9
engine/core/src/CPUCount.cpp
Normal file
9
engine/core/src/CPUCount.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "CPUCount.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
unsigned int RbxTotalUsableCoreCount(unsigned int defaultValue)
|
||||
{
|
||||
unsigned n = std::thread::hardware_concurrency();
|
||||
return n ? n : defaultValue;
|
||||
}
|
||||
3
engine/core/src/CPUCount.hpp
Normal file
3
engine/core/src/CPUCount.hpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
unsigned int RbxTotalUsableCoreCount(unsigned int defaultValue);
|
||||
121
engine/core/src/Coordinator.cpp
Normal file
121
engine/core/src/Coordinator.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
#include "Coordinator.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using namespace Tasks;
|
||||
|
||||
bool Barrier::isInhibited(TaskScheduler::Job* job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
return jobs[job] > counter;
|
||||
}
|
||||
|
||||
void Barrier::onPostStep(TaskScheduler::Job* job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
unsigned int& count(jobs[job]);
|
||||
|
||||
// Note: we could almost assert that count==counter, but
|
||||
// there may be edge cases where a step slips through
|
||||
// the cracks
|
||||
if (count == counter)
|
||||
{
|
||||
count++;
|
||||
if (--remainingTasks == 0)
|
||||
releaseBarrier();
|
||||
}
|
||||
}
|
||||
|
||||
void Barrier::releaseBarrier()
|
||||
{
|
||||
remainingTasks = int(jobs.size());
|
||||
counter++;
|
||||
}
|
||||
|
||||
void Barrier::onAdded(TaskScheduler::Job* job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
AYAASSERT(jobs.find(job) == jobs.end());
|
||||
|
||||
jobs[job] = counter;
|
||||
remainingTasks++;
|
||||
|
||||
AYAASSERT(remainingTasks <= jobs.size());
|
||||
}
|
||||
|
||||
void Barrier::onRemoved(TaskScheduler::Job* job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
AYAASSERT(jobs.find(job) != jobs.end());
|
||||
|
||||
if (jobs[job] <= counter)
|
||||
remainingTasks--;
|
||||
|
||||
jobs.erase(job);
|
||||
|
||||
AYAASSERT(remainingTasks <= jobs.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool SequenceBase::isInhibited(TaskScheduler::Job* job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
if (nextJobIndex < jobs.size())
|
||||
return jobs[nextJobIndex] != job;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void SequenceBase::advance()
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
if (++nextJobIndex == jobs.size())
|
||||
nextJobIndex = 0;
|
||||
}
|
||||
|
||||
void SequenceBase::onAdded(TaskScheduler::Job* job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
jobs.push_back(job);
|
||||
}
|
||||
|
||||
void SequenceBase::onRemoved(TaskScheduler::Job* job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
for (size_t i = 0; i < jobs.size(); ++i)
|
||||
if (jobs[i] == job)
|
||||
{
|
||||
jobs.erase(jobs.begin() + i);
|
||||
if (nextJobIndex > i)
|
||||
--nextJobIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Exclusive::isInhibited(TaskScheduler::Job* job)
|
||||
{
|
||||
AYAASSERT(job != runningJob);
|
||||
return runningJob != NULL;
|
||||
}
|
||||
|
||||
void Exclusive::onPreStep(TaskScheduler::Job* job)
|
||||
{
|
||||
AYAASSERT(runningJob == NULL);
|
||||
runningJob = job;
|
||||
}
|
||||
|
||||
void Exclusive::onPostStep(TaskScheduler::Job* job)
|
||||
{
|
||||
AYAASSERT(runningJob == job);
|
||||
runningJob = NULL;
|
||||
}
|
||||
115
engine/core/src/Coordinator.hpp
Normal file
115
engine/core/src/Coordinator.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TaskScheduler.hpp"
|
||||
#include <map>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Tasks
|
||||
{
|
||||
// Prevents jobs from running according to some coordination logic.
|
||||
// Generally a Coordinator will affect execution order but not
|
||||
// affect parallelism. It may be more efficient to enforce resource
|
||||
// locks by specializing the TaskScheduler. See the DataModel scheduler.
|
||||
class AyaBaseClass Coordinator
|
||||
{
|
||||
public:
|
||||
// These functions must be written in a thread-safe manner
|
||||
// However, no 2 threads will call a function with the same job
|
||||
|
||||
virtual bool isInhibited(TaskScheduler::Job* job) = 0;
|
||||
virtual void onPreStep(TaskScheduler::Job* job) {}
|
||||
virtual void onPostStep(TaskScheduler::Job* job) {}
|
||||
virtual void onAdded(TaskScheduler::Job* job) {}
|
||||
virtual void onRemoved(TaskScheduler::Job* job) {}
|
||||
};
|
||||
|
||||
// Prevents jobs from running in parallel
|
||||
class Exclusive : public Coordinator
|
||||
{
|
||||
volatile TaskScheduler::Job* runningJob;
|
||||
|
||||
public:
|
||||
Exclusive()
|
||||
: runningJob(NULL)
|
||||
{
|
||||
}
|
||||
virtual bool isInhibited(TaskScheduler::Job* job);
|
||||
virtual void onPreStep(TaskScheduler::Job* job);
|
||||
virtual void onPostStep(TaskScheduler::Job* job);
|
||||
virtual void onAdded(TaskScheduler::Job* job) {}
|
||||
virtual void onRemoved(TaskScheduler::Job* job) {}
|
||||
};
|
||||
|
||||
// Requires that all coordinated jobs finish stepping before any job steps again.
|
||||
// It is the task-equivalent of a thread barrier.
|
||||
class Barrier : public Coordinator
|
||||
{
|
||||
unsigned int counter;
|
||||
unsigned int remainingTasks;
|
||||
Aya::mutex mutex;
|
||||
std::map<TaskScheduler::Job*, unsigned int> jobs;
|
||||
|
||||
void releaseBarrier();
|
||||
|
||||
public:
|
||||
Barrier()
|
||||
: counter(0)
|
||||
, remainingTasks(0)
|
||||
{
|
||||
}
|
||||
virtual bool isInhibited(TaskScheduler::Job* job);
|
||||
virtual void onPostStep(TaskScheduler::Job* job);
|
||||
virtual void onAdded(TaskScheduler::Job* job);
|
||||
virtual void onRemoved(TaskScheduler::Job* job);
|
||||
};
|
||||
|
||||
class SequenceBase : public Coordinator
|
||||
{
|
||||
private:
|
||||
unsigned int nextJobIndex;
|
||||
Aya::mutex mutex;
|
||||
std::vector<TaskScheduler::Job*> jobs;
|
||||
|
||||
protected:
|
||||
void advance();
|
||||
|
||||
public:
|
||||
SequenceBase()
|
||||
: nextJobIndex(0)
|
||||
{
|
||||
}
|
||||
virtual bool isInhibited(TaskScheduler::Job* job);
|
||||
virtual void onAdded(TaskScheduler::Job* job);
|
||||
virtual void onRemoved(TaskScheduler::Job* job);
|
||||
};
|
||||
|
||||
// Requires that all coordinated jobs execute in sequence.
|
||||
// Jobs are allowed to run in parallel, but they must start
|
||||
// execution in the sequence in which they are added to the
|
||||
// coordinator
|
||||
class Sequence : public SequenceBase
|
||||
{
|
||||
public:
|
||||
virtual void onPreStep(TaskScheduler::Job* job)
|
||||
{
|
||||
advance();
|
||||
}
|
||||
};
|
||||
|
||||
// Requires that all coordinated jobs execute in sequence.
|
||||
// Jobs are *not* allowed to run in parallel.
|
||||
// This is equivalent to Exclusive and Sequence combined
|
||||
class ExclusiveSequence : public SequenceBase
|
||||
{
|
||||
public:
|
||||
virtual void onPostStep(TaskScheduler::Job* job)
|
||||
{
|
||||
advance();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Tasks
|
||||
} // namespace Aya
|
||||
38
engine/core/src/Countable.hpp
Normal file
38
engine/core/src/Countable.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "atomic.hpp"
|
||||
#include "Declarations.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace Diagnostics
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
class AyaBaseClass Countable
|
||||
{
|
||||
static Aya::atomic<int> count;
|
||||
|
||||
public:
|
||||
static long getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
~Countable()
|
||||
{
|
||||
--count;
|
||||
}
|
||||
|
||||
protected:
|
||||
Countable()
|
||||
{
|
||||
++count;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
Aya::atomic<int> Countable<T>::count;
|
||||
|
||||
} // namespace Diagnostics
|
||||
|
||||
} // namespace Aya
|
||||
105
engine/core/src/Crypt.cpp
Normal file
105
engine/core/src/Crypt.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "Crypt.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#include <boost/scoped_array.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
// The stuff between "-----BEGIN PUBLIC KEY-----" is base64 encoded string, the key is also base64 encoded & should work.
|
||||
|
||||
// Public/Private Key Pair generated by Ben D from the public/private blobs
|
||||
// TODO Load dynamically
|
||||
char publicKey[] = {"-----BEGIN PUBLIC KEY-----\n"
|
||||
|
||||
"-----END PUBLIC KEY-----\n"};
|
||||
|
||||
// Helper Fn for Base64 Decode, pass out the unsigned char** with the caller responsibility to free out
|
||||
int unbase64(char* input, unsigned char** out)
|
||||
{
|
||||
BIO *b64, *bmem;
|
||||
|
||||
// base64 encoding is longer than the original 'string'
|
||||
int length = strlen(input);
|
||||
unsigned char* buffer = (unsigned char*)malloc(length);
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
|
||||
bmem = BIO_new_mem_buf(input, length);
|
||||
bmem = BIO_push(b64, bmem);
|
||||
|
||||
length = BIO_read(bmem, buffer, length);
|
||||
buffer[length] = 0;
|
||||
|
||||
BIO_free_all(bmem);
|
||||
|
||||
*out = buffer; // caller frees
|
||||
return length;
|
||||
}
|
||||
|
||||
// verify that the plaintext message is matching signature (base64 encoded)
|
||||
int verifyMessage(char* message, char* sig_b64)
|
||||
{
|
||||
SHA512_CTX sha512_ctx = {0};
|
||||
unsigned char digest[SHA512_DIGEST_LENGTH];
|
||||
int rc = 1;
|
||||
|
||||
rc = SHA512_Init(&sha512_ctx);
|
||||
if (1 != rc)
|
||||
throw Aya::runtime_error("Error during SHA512_Init");
|
||||
|
||||
rc = SHA512_Update(&sha512_ctx, (unsigned char*)message, strlen(message));
|
||||
if (1 != rc)
|
||||
throw Aya::runtime_error("Error during SHA512_Update");
|
||||
|
||||
rc = SHA512_Final(digest, &sha512_ctx);
|
||||
if (1 != rc)
|
||||
throw Aya::runtime_error("Error during SHA512_Final");
|
||||
|
||||
unsigned char* signature;
|
||||
int length = unbase64(sig_b64, &signature);
|
||||
|
||||
// WARNING: no error checking for brevity
|
||||
BIO* bio = BIO_new_mem_buf(publicKey, sizeof(publicKey));
|
||||
|
||||
RSA* rsa_key = 0;
|
||||
rsa_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
|
||||
|
||||
int ret = RSA_verify(NID_sha512, digest, SHA512_DIGEST_LENGTH, signature, length, rsa_key);
|
||||
|
||||
ERR_print_errors_fp(stdout);
|
||||
|
||||
RSA_free(rsa_key);
|
||||
BIO_free(bio);
|
||||
free(signature);
|
||||
|
||||
return ret;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
Crypt::Crypt() {}
|
||||
|
||||
Crypt::~Crypt() {}
|
||||
|
||||
// Crypt implementation with RSA security using SHA1 algorithm for signature verification.
|
||||
void Crypt::verifySignatureBase64(std::string messageStr, std::string signatureStr)
|
||||
{
|
||||
#ifndef AYA_TEST_BUILD
|
||||
if (!verifyMessage((char*)messageStr.c_str(), (char*)signatureStr.c_str()))
|
||||
throw std::runtime_error(""); // message hidden on purpose - prevent reverse engineering
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
20
engine/core/src/Crypt.hpp
Normal file
20
engine/core/src/Crypt.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32) && !defined(AYA_PLATFORM_DURANGO)
|
||||
#include <Windows.h>
|
||||
#include <wincrypt.h>
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class Crypt
|
||||
{
|
||||
|
||||
public:
|
||||
Crypt();
|
||||
~Crypt();
|
||||
void verifySignatureBase64(std::string message, std::string signatureBase64);
|
||||
};
|
||||
} // namespace Aya
|
||||
51
engine/core/src/Debug.cpp
Normal file
51
engine/core/src/Debug.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
// d9mz - might be something wrong with my sdk, but this is needed to compile
|
||||
// shadercompiler - it sucks
|
||||
#ifndef PTRDIFF_MAX
|
||||
#define PTRDIFF_MAX 9223372036854775807
|
||||
#endif
|
||||
|
||||
#include "Debug.hpp"
|
||||
#include "AyaAssert.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Debugapi.h>
|
||||
#endif
|
||||
const int CRASHONASSERT = 255;
|
||||
|
||||
void ReleaseAssert(int channel, const char* msg)
|
||||
{
|
||||
if (channel == CRASHONASSERT)
|
||||
AYACRASH(msg);
|
||||
else
|
||||
FLog::FastLog(channel, msg, 0);
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// overload this in the debugger to pass by the crash
|
||||
volatile bool Debugable::doCrashEnabled = true;
|
||||
|
||||
void Debugable::doCrash()
|
||||
{
|
||||
if (doCrashEnabled)
|
||||
{
|
||||
DebugBreak();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
void Debugable::doCrash(const char* message)
|
||||
{
|
||||
if (doCrashEnabled)
|
||||
{
|
||||
DebugBreak();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma optimize("", on)
|
||||
|
||||
} // namespace Aya
|
||||
207
engine/core/src/Debug.hpp
Normal file
207
engine/core/src/Debug.hpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
|
||||
#include "AyaPlatform.hpp"
|
||||
#include "AyaAssert.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#include <set>
|
||||
#include <ostream>
|
||||
#include <fstream>
|
||||
#include <assert.h>
|
||||
|
||||
#if (defined(_DEBUG) && defined(_WIN32))
|
||||
#include <crtdbg.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <typeinfo>
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#include "Declarations.hpp"
|
||||
#include "FastLog.hpp"
|
||||
|
||||
#ifndef _WIN32
|
||||
#define __noop
|
||||
inline void DebugBreak()
|
||||
{
|
||||
#if defined(__i386__)
|
||||
// gcc on intel
|
||||
__asm__ __volatile__("int $3");
|
||||
#else
|
||||
// some other gcc
|
||||
::abort();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
LOGGROUP(Asserts)
|
||||
|
||||
/* Overview of builds and switches:
|
||||
|
||||
AYAASSERT: Standard assert. Should be reasonably fast. Do not do "finds" or complex stuff here. Simple bools,
|
||||
simple math, a couple levels of pointer indirection, etc. AYAASSERT_VERY_FAST: High fr equency, extremely fast assert. Not in regular debug
|
||||
build because frequency too high. Mostly inner engine stuff AYAASSERT_SLOW: Put things like "find" here. Will always run
|
||||
in debug builds AYAASSERT_IF_VALIDATING: Very slow stuff. Only turns on if the "validating debug" switch is turned on in debug or noOpt build
|
||||
AYAASSERT_FISHING: Usually doesn't go off, should be safe - turn on for engine testing
|
||||
|
||||
AYAASSERT() AYAASSERT_VERY_FAST() AYAASSERT_SLOW()
|
||||
AYAASSERT_IF_VALIDATING() AYAASSERT_FISHING() DEBUG X X
|
||||
X X - NoOpt
|
||||
X X - -
|
||||
- ReleaseAssert X - -
|
||||
- - Release - -
|
||||
- - -
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define __AYA_VERY_FAST_ASSERT
|
||||
#define __AYA_VALIDATE_ASSERT
|
||||
// #define __AYA_SLOW_ASSERT // TODO: Hire a physics guy to enable them
|
||||
// #define __AYA_FISHING_ASSERT
|
||||
#define __AYA_NOT_RELEASE
|
||||
#endif
|
||||
|
||||
#ifdef _NOOPT
|
||||
#define __AYA_CRASH_ON_ASSERT
|
||||
#define __AYA_VERY_FAST_ASSERT
|
||||
#define __AYA_NOT_RELEASE
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
// Used for memory leak detection and other stuff
|
||||
class Debugable
|
||||
{
|
||||
public:
|
||||
// this is here as a last chance way to debug an assert build, force assertions on, but not crash
|
||||
static volatile bool doCrashEnabled;
|
||||
|
||||
static void doCrash();
|
||||
static void doCrash(const char*);
|
||||
|
||||
static void* badMemory()
|
||||
{
|
||||
return reinterpret_cast<void*>(0x00000003);
|
||||
} // set values to this when deleting to check if ever coming back
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
void AYACRASH();
|
||||
void AYACRASH(const char* message);
|
||||
|
||||
void ReleaseAssert(int channel, const char* msg);
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
|
||||
// macro to convince a compiler a variable is used while not generating instructions (useful for removing warnings)
|
||||
#define AYA_UNUSED(x) (void)(sizeof((x), 0))
|
||||
|
||||
// This macro will cause a crash. Usually you don't call it directly. Use AYAASSERT instead
|
||||
#define AYA_CRASH_ASSERT(expr) ((void)(!!(expr) || (Aya::Debugable::doCrash(#expr), 0)))
|
||||
|
||||
// This macro will just log an assert string, if we will run into crash log with the assert information will be sent to us
|
||||
#define AYA_LOG_ASSERT(expr)
|
||||
|
||||
// LEGACY_ASSERT should be used when we have some assert bogging us and it seems like this guy is a good candidate for removal
|
||||
// usage just replace AYAASSERT with LEGACY_ASSERT and it will gone by default, but if you need to see it temporary define FIRE_LEGACY_ASSERT
|
||||
#undef FIRE_LEGACY_ASSERT
|
||||
|
||||
#ifdef FIRE_LEGACY_ASSERT
|
||||
#define LEGACY_ASSERT(expr) AYAASSERT(expr)
|
||||
#else
|
||||
#define LEGACY_ASSERT(expr) ((void)0)
|
||||
#endif
|
||||
|
||||
#define AYAASSERTENABLED
|
||||
|
||||
// AYAASSERT()
|
||||
//
|
||||
#ifdef __AYA_CRASH_ON_ASSERT
|
||||
#define AYAASSERT AYA_CRASH_ASSERT
|
||||
#else
|
||||
#if (defined(_DEBUG) && defined(__APPLE__)) // Apple Debug
|
||||
#include "TargetConditionals.hpp"
|
||||
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
||||
#define AYAASSERT AYA_LOG_ASSERT // iOS has no way to step over asserts (makes debugging hard)
|
||||
#else
|
||||
#define AYAASSERT(expr) assert(expr)
|
||||
#define AYAASSERTENABLED
|
||||
#endif
|
||||
#elif (defined(_DEBUG) && defined(_WIN32)) // Windows Debug
|
||||
/*
|
||||
#define AYAASSERT(expr) \
|
||||
((void) (!!(expr) || \
|
||||
((Aya::_internal::_debugHook != NULL) && (Aya::_internal::_debugHook(#expr, __FILE__, __LINE__))) || \
|
||||
(_ASSERTE(expr), 0)))
|
||||
*/
|
||||
#define AYAASSERT(expr) assert(expr)
|
||||
#define AYAASSERTENABLED
|
||||
#else // All Platform Release
|
||||
#define AYAASSERT AYA_LOG_ASSERT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// AYAASSERT_VERY_FAST()
|
||||
//
|
||||
#ifdef __AYA_VERY_FAST_ASSERT
|
||||
#define AYAASSERT_VERY_FAST(expr) AYAASSERT(expr)
|
||||
#else
|
||||
#define AYAASSERT_VERY_FAST(expr) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
// AYAASSERT_SLOW()
|
||||
//
|
||||
#ifdef __AYA_SLOW_ASSERT
|
||||
#define AYAASSERT_SLOW(expr) AYAASSERT(expr)
|
||||
#else
|
||||
#define AYAASSERT_SLOW(expr) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
// AYAASSERT_FISHING)
|
||||
//
|
||||
#ifdef __AYA_FISHING_ASSERT
|
||||
#define AYAASSERT_FISHING(expr) AYAASSERT(expr)
|
||||
#else
|
||||
#define AYAASSERT_FISHING(expr) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
// AYAASSERT_IF_VALIDATING()
|
||||
//
|
||||
#ifdef __AYA_VALIDATE_ASSERT
|
||||
#define AYAASSERT_IF_VALIDATING(expr) AYAASSERT((expr))
|
||||
|
||||
#else
|
||||
#define AYAASSERT_IF_VALIDATING(expr) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
// AYAASSERT_NOT_RELEASE() make sure this code is not being compiled in release build
|
||||
#ifdef __AYA_NOT_RELEASE
|
||||
#define AYAASSERT_NOT_RELEASE() ((void)0)
|
||||
#else
|
||||
#define AYAASSERT_NOT_RELEASE() AYACRASH()
|
||||
#endif
|
||||
|
||||
|
||||
// Same as boost::polymorphic_downcast but with an AYAASSERT
|
||||
template<class T, class U>
|
||||
inline T aya_static_cast(U u)
|
||||
{
|
||||
AYAASSERT_SLOW(dynamic_cast<T>(u) == u);
|
||||
return static_cast<T>(u);
|
||||
}
|
||||
55
engine/core/src/Declarations.hpp
Normal file
55
engine/core/src/Declarations.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
|
||||
http://msdn.microsoft.com/en-us/magazine/cc301398.aspx
|
||||
|
||||
These days abstract classes are
|
||||
not just common, they're ubiquitous. Think: where in Windows® do
|
||||
abstract classes appear over and over again? That's right, in COM!
|
||||
A COM interface is an abstract class with only pure virtual functions.
|
||||
As everything in Windows migrates to COM land, Windows-based programs
|
||||
have COM interfaces up the wazoo. A typical COM class might implement
|
||||
a dozen or more interfaces, each with several functions.
|
||||
Even outside COM, the notion of an interface is quite powerful
|
||||
and useful, as in the Java language. Each interface implementation might
|
||||
use several layers of classes, none intended to be used by themselves,
|
||||
but only as base classes for yet more classes. ATL provides many such
|
||||
classes using templates, another source of class proliferation. All of
|
||||
this adds up to lots of initialization code and useless vtables with
|
||||
NULL entries. The total bloat can become significant, especially when
|
||||
you're developing small objects that must load over a slow medium like
|
||||
the Internet.
|
||||
So __declspec(novtable) was invented to solve the problem. It's a
|
||||
Microsoft-specific optimization hint that tells the compiler: this class
|
||||
is never used by itself, but only as a base class for other classes, so
|
||||
don't bother with all that vtable stuff, thank you.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Decoration to indicate a class is to be treated as an "Interface"
|
||||
// The class should contain pure virtual functions and maybe a little
|
||||
// trivial code. Otherwise, use AyaBaseClass.
|
||||
// !!! You can't define a virtual destructor for a class of this type
|
||||
#define AyaInterface __declspec(novtable)
|
||||
|
||||
// Decoration to indicate a class should not be instantiated directly
|
||||
// !!! You can't define a virtual destructor for a class of this type
|
||||
#define AyaBaseClass __declspec(novtable)
|
||||
|
||||
/****
|
||||
Note:
|
||||
|
||||
C++ doesn't have a strict "Interface" type. AyaInterface should be used
|
||||
for classes that declare only pure virtual functions and maybe a constructor
|
||||
and/or a field. Classes that define non-trivial code should use AyaBaseClass instead.
|
||||
|
||||
***/
|
||||
|
||||
#else
|
||||
|
||||
#define AyaInterface
|
||||
#define AyaBaseClass
|
||||
|
||||
#endif
|
||||
370
engine/core/src/DenseHash.hpp
Normal file
370
engine/core/src/DenseHash.hpp
Normal file
@@ -0,0 +1,370 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// Internal implementation of DenseHashSet and DenseHashMap
|
||||
namespace detail
|
||||
{
|
||||
template<typename Key>
|
||||
struct DenseHashSetItem
|
||||
{
|
||||
Key key;
|
||||
|
||||
DenseHashSetItem(const Key& key)
|
||||
: key(key)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Key, typename Value>
|
||||
struct DenseHashMapItem
|
||||
{
|
||||
Key key;
|
||||
Value value;
|
||||
|
||||
DenseHashMapItem(const Key& key)
|
||||
: key(key)
|
||||
, value()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Key, typename Item, typename Hash, typename Eq>
|
||||
class DenseHashTable
|
||||
{
|
||||
public:
|
||||
class const_iterator;
|
||||
|
||||
DenseHashTable(const Key& empty_key, size_t buckets = 0)
|
||||
: data(buckets, Item(empty_key))
|
||||
, count(0)
|
||||
, empty_key(empty_key)
|
||||
{
|
||||
// buckets has to be power-of-two or zero
|
||||
AYAASSERT((buckets & (buckets - 1)) == 0);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
data.clear();
|
||||
count = 0;
|
||||
}
|
||||
|
||||
Item* insert(const Key& key)
|
||||
{
|
||||
// It is invalid to insert empty_key into the table since it acts as a "entry does not exist" marker
|
||||
AYAASSERT(!eq(key, empty_key));
|
||||
|
||||
if (count >= data.size() * 3 / 4)
|
||||
{
|
||||
rehash();
|
||||
}
|
||||
|
||||
size_t hashmod = data.size() - 1;
|
||||
size_t bucket = hasher(key) & hashmod;
|
||||
|
||||
for (size_t probe = 0; probe <= hashmod; ++probe)
|
||||
{
|
||||
Item& probe_item = data[bucket];
|
||||
|
||||
// Element does not exist, insert here
|
||||
if (eq(probe_item.key, empty_key))
|
||||
{
|
||||
probe_item.key = key;
|
||||
count++;
|
||||
return &probe_item;
|
||||
}
|
||||
|
||||
// Element already exists
|
||||
if (eq(probe_item.key, key))
|
||||
{
|
||||
return &probe_item;
|
||||
}
|
||||
|
||||
// Hash collision, quadratic probing
|
||||
bucket = (bucket + probe + 1) & hashmod;
|
||||
}
|
||||
|
||||
// Hash table is full - this should not happen
|
||||
AYAASSERT(false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const Item* find(const Key& key) const
|
||||
{
|
||||
if (data.empty())
|
||||
return 0;
|
||||
if (eq(key, empty_key))
|
||||
return 0;
|
||||
|
||||
size_t hashmod = data.size() - 1;
|
||||
size_t bucket = hasher(key) & hashmod;
|
||||
|
||||
for (size_t probe = 0; probe <= hashmod; ++probe)
|
||||
{
|
||||
const Item& probe_item = data[bucket];
|
||||
|
||||
// Element exists
|
||||
if (eq(probe_item.key, key))
|
||||
return &probe_item;
|
||||
|
||||
// Element does not exist
|
||||
if (eq(probe_item.key, empty_key))
|
||||
return NULL;
|
||||
|
||||
// Hash collision, quadratic probing
|
||||
bucket = (bucket + probe + 1) & hashmod;
|
||||
}
|
||||
|
||||
// Hash table is full - this should not happen
|
||||
AYAASSERT(false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
size_t start = 0;
|
||||
|
||||
while (start < data.size() && eq(data[start].key, empty_key))
|
||||
start++;
|
||||
|
||||
return const_iterator(this, start);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(this, data.size());
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t bucket_count() const
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
|
||||
class const_iterator
|
||||
{
|
||||
public:
|
||||
const_iterator()
|
||||
: set(0)
|
||||
, index(0)
|
||||
{
|
||||
}
|
||||
|
||||
const_iterator(const DenseHashTable<Key, Item, Hash, Eq>* set, size_t index)
|
||||
: set(set)
|
||||
, index(index)
|
||||
{
|
||||
}
|
||||
|
||||
const Item& getItem() const
|
||||
{
|
||||
return set->data[index];
|
||||
}
|
||||
|
||||
const Key& operator*() const
|
||||
{
|
||||
return set->data[index].key;
|
||||
}
|
||||
|
||||
const Key* operator->() const
|
||||
{
|
||||
return &set->data[index].key;
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator& other) const
|
||||
{
|
||||
return set == other.set && index == other.index;
|
||||
}
|
||||
|
||||
bool operator!=(const const_iterator& other) const
|
||||
{
|
||||
return set != other.set || index != other.index;
|
||||
}
|
||||
|
||||
const_iterator& operator++()
|
||||
{
|
||||
size_t size = set->data.size();
|
||||
|
||||
do
|
||||
{
|
||||
index++;
|
||||
} while (index < size && set->eq(set->data[index].key, set->empty_key));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator operator++(int)
|
||||
{
|
||||
const_iterator res = *this;
|
||||
++*this;
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
const DenseHashTable<Key, Item, Hash, Eq>* set;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<Item> data;
|
||||
size_t count;
|
||||
Key empty_key;
|
||||
Hash hasher;
|
||||
Eq eq;
|
||||
|
||||
void rehash()
|
||||
{
|
||||
size_t newsize = data.empty() ? 16 : data.size() * 2;
|
||||
DenseHashTable newtable(empty_key, newsize);
|
||||
|
||||
for (size_t i = 0; i < data.size(); ++i)
|
||||
if (!eq(data[i].key, empty_key))
|
||||
*newtable.insert(data[i].key) = data[i];
|
||||
|
||||
AYAASSERT(count == newtable.count);
|
||||
data.swap(newtable.data);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// This is a faster alternative of boost::unordered_set, but it does not implement the same interface (i.e. it does not support erasing and has
|
||||
// contains() instead of find())
|
||||
template<typename Key, typename Hash = boost::hash<Key>, typename Eq = std::equal_to<Key>>
|
||||
class DenseHashSet
|
||||
{
|
||||
typedef detail::DenseHashTable<Key, detail::DenseHashSetItem<Key>, Hash, Eq> Impl;
|
||||
Impl impl;
|
||||
|
||||
public:
|
||||
typedef typename Impl::const_iterator const_iterator;
|
||||
|
||||
DenseHashSet(const Key& empty_key, size_t buckets = 0)
|
||||
: impl(empty_key, buckets)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
impl.clear();
|
||||
}
|
||||
|
||||
void insert(const Key& key)
|
||||
{
|
||||
impl.insert(key);
|
||||
}
|
||||
|
||||
bool contains(const Key& key) const
|
||||
{
|
||||
return impl.find(key) != 0;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return impl.size();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return impl.size() == 0;
|
||||
}
|
||||
|
||||
size_t bucket_count() const
|
||||
{
|
||||
return impl.bucket_count();
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return impl.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return impl.end();
|
||||
}
|
||||
};
|
||||
|
||||
// This is a faster alternative of boost::unordered_map, but it does not implement the same interface (i.e. it does not support erasing and has
|
||||
// contains() instead of find())
|
||||
template<typename Key, typename Value, typename Hash = boost::hash<Key>, typename Eq = std::equal_to<Key>>
|
||||
class DenseHashMap
|
||||
{
|
||||
typedef detail::DenseHashTable<Key, detail::DenseHashMapItem<Key, Value>, Hash, Eq> Impl;
|
||||
Impl impl;
|
||||
|
||||
public:
|
||||
typedef typename Impl::const_iterator const_iterator;
|
||||
|
||||
DenseHashMap(const Key& empty_key, size_t buckets = 0)
|
||||
: impl(empty_key, buckets)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
impl.clear();
|
||||
}
|
||||
|
||||
// Note: this reference is invalidated by any insert operation (i.e. operator[])
|
||||
Value& operator[](const Key& key)
|
||||
{
|
||||
return impl.insert(key)->value;
|
||||
}
|
||||
|
||||
// Note: this pointer is invalidated by any insert operation (i.e. operator[])
|
||||
const Value* find(const Key& key) const
|
||||
{
|
||||
const detail::DenseHashMapItem<Key, Value>* result = impl.find(key);
|
||||
|
||||
return result ? &result->value : NULL;
|
||||
}
|
||||
|
||||
// Note: this pointer is invalidated by any insert operation (i.e. operator[])
|
||||
Value* find(const Key& key)
|
||||
{
|
||||
const detail::DenseHashMapItem<Key, Value>* result = impl.find(key);
|
||||
|
||||
return result ? const_cast<Value*>(&result->value) : NULL;
|
||||
}
|
||||
|
||||
bool contains(const Key& key) const
|
||||
{
|
||||
return impl.find(key) != 0;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return impl.size();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return impl.size() == 0;
|
||||
}
|
||||
|
||||
size_t bucket_count() const
|
||||
{
|
||||
return impl.bucket_count();
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return impl.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return impl.end();
|
||||
}
|
||||
};
|
||||
} // namespace Aya
|
||||
816
engine/core/src/FastLog.cpp
Normal file
816
engine/core/src/FastLog.cpp
Normal file
@@ -0,0 +1,816 @@
|
||||
#include "FastLog.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#elif __ANDROID__
|
||||
#include <sys/atomics.h>
|
||||
#elif defined(__linux) || defined(__APPLE__)
|
||||
#include <atomic>
|
||||
#else
|
||||
#include <libkern/OSAtomic.h>
|
||||
#endif
|
||||
#include "signal.hpp"
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
|
||||
#include "atomic.hpp"
|
||||
#include "time.hpp"
|
||||
#include "boost.hpp"
|
||||
#include "AyaPlatform.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
|
||||
#include "Utility/Statistics.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
|
||||
#define FLOAT_MARKER 0xFFFF10AD
|
||||
|
||||
#define MAX_LOG_MESSAGE 260
|
||||
|
||||
LOGVARIABLE(FastLogValueChanged, 1)
|
||||
|
||||
namespace FLog
|
||||
{
|
||||
|
||||
struct IValueGetSet
|
||||
{
|
||||
public:
|
||||
virtual void set(const std::string& valueData, FastVarType _fflagMask) = 0;
|
||||
virtual std::string get() = 0;
|
||||
virtual FastVarType getType() = 0;
|
||||
virtual void setIsSync(bool) = 0;
|
||||
virtual bool getIsSync() = 0;
|
||||
virtual bool* getIsSyncAddr() = 0;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
T convertFromString(const std::string& valueData);
|
||||
|
||||
template<>
|
||||
int convertFromString<int>(const std::string& valueData)
|
||||
{
|
||||
return atoi(valueData.c_str());
|
||||
}
|
||||
|
||||
template<>
|
||||
bool convertFromString<bool>(const std::string& valueData)
|
||||
{
|
||||
return (valueData == "true" || valueData == "True") ? true : false;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string convertFromString<std::string>(const std::string& valueData)
|
||||
{
|
||||
return valueData;
|
||||
}
|
||||
|
||||
template<>
|
||||
Channel convertFromString<unsigned char>(const std::string& valueData)
|
||||
{
|
||||
return (unsigned char)atoi(valueData.c_str());
|
||||
}
|
||||
|
||||
std::string convertToString(bool a)
|
||||
{
|
||||
return a ? "true" : "false";
|
||||
}
|
||||
|
||||
std::string convertToString(int a)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
char temp[200];
|
||||
snprintf(temp, 200 - 1, "%d", a);
|
||||
return temp;
|
||||
#pragma warning(pop)
|
||||
}
|
||||
std::string convertToString(std::string a)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
class ValueGetSet : public IValueGetSet
|
||||
{
|
||||
T* value;
|
||||
FastVarType fastVarType;
|
||||
bool isSync;
|
||||
|
||||
public:
|
||||
ValueGetSet(T* value, FastVarType _fastVarType)
|
||||
: value(value)
|
||||
, fastVarType(_fastVarType)
|
||||
, isSync(true)
|
||||
{
|
||||
}
|
||||
|
||||
void set(const std::string& valueData, FastVarType _fflagMask)
|
||||
{
|
||||
if (_fflagMask != FASTVARTYPE_ANY)
|
||||
{
|
||||
// assert(fastVarType == _fflagMask);
|
||||
if (fastVarType != _fflagMask)
|
||||
return;
|
||||
}
|
||||
|
||||
*value = convertFromString<T>(valueData);
|
||||
}
|
||||
|
||||
std::string get()
|
||||
{
|
||||
return convertToString(*value);
|
||||
}
|
||||
|
||||
FastVarType getType()
|
||||
{
|
||||
return fastVarType;
|
||||
}
|
||||
|
||||
void setIsSync(bool sync)
|
||||
{
|
||||
isSync = sync;
|
||||
}
|
||||
|
||||
bool getIsSync()
|
||||
{
|
||||
return isSync;
|
||||
}
|
||||
|
||||
bool* getIsSyncAddr()
|
||||
{
|
||||
return &isSync;
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::unordered_map<std::string, IValueGetSet*> Variables;
|
||||
typedef boost::unordered_map<std::string, std::string> UnknownVariables;
|
||||
|
||||
Variables* gVariables;
|
||||
UnknownVariables* gUnknownVariables;
|
||||
|
||||
|
||||
typedef boost::unordered_map<std::string, Channel*> LogGroups;
|
||||
LogGroups* gLogGroups = 0;
|
||||
LogGroups* gDynamicLogGroups = 0;
|
||||
|
||||
typedef boost::unordered_map<std::string, Channel> UnknownLogGroups;
|
||||
UnknownLogGroups* gUnknownLogGroups = 0;
|
||||
|
||||
typedef boost::unordered_map<std::string, bool*> FastFlags;
|
||||
FastFlags* gFastFlags = 0;
|
||||
FastFlags* gDynamicFastFlags = 0;
|
||||
|
||||
typedef boost::unordered_map<std::string, bool> UnknownFastFlags;
|
||||
UnknownFastFlags* gUnknownFastFlags = 0;
|
||||
|
||||
|
||||
#if defined(__aarch64__)
|
||||
typedef double FASTFLAG_VAR_FLOAT;
|
||||
#else
|
||||
typedef float FASTFLAG_VAR_FLOAT;
|
||||
#endif
|
||||
|
||||
double timeDummy()
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
TimeFunc timeF = timeDummy;
|
||||
|
||||
ExternalLogFunc logF = NULL;
|
||||
|
||||
|
||||
// Exactly 32 bytes on 32bit platform, add stuff responsibly
|
||||
struct LogEntry
|
||||
{
|
||||
const char* message;
|
||||
float timestamp;
|
||||
unsigned threadid;
|
||||
union Args
|
||||
{
|
||||
struct IntArgs
|
||||
{
|
||||
const void* arg0;
|
||||
const void* arg1;
|
||||
const void* arg2;
|
||||
const void* arg3;
|
||||
const void* arg4;
|
||||
} intArgs;
|
||||
struct FloatArgs
|
||||
{
|
||||
FASTFLAG_VAR_FLOAT arg0f;
|
||||
FASTFLAG_VAR_FLOAT arg1f;
|
||||
FASTFLAG_VAR_FLOAT arg2f;
|
||||
FASTFLAG_VAR_FLOAT arg3f;
|
||||
FASTFLAG_VAR_FLOAT arg4f;
|
||||
} floatArgs;
|
||||
|
||||
char sarg[sizeof(void*) * 5];
|
||||
} args;
|
||||
};
|
||||
|
||||
|
||||
static LogEntry g_FastLog[LOGCHANNELS][LOG_HISTORY];
|
||||
|
||||
static Aya::atomic<unsigned int> g_LogCounters[LOGCHANNELS] = {0, 0, 0, 0, 0};
|
||||
|
||||
// flf == FastLogFloat
|
||||
inline void* flf(FASTFLAG_VAR_FLOAT value)
|
||||
{
|
||||
return reinterpret_cast<void*(&)>(value);
|
||||
}
|
||||
|
||||
inline float flfBack(const void* v)
|
||||
{
|
||||
return static_cast<float>(reinterpret_cast<FASTFLAG_VAR_FLOAT&>(v));
|
||||
}
|
||||
|
||||
void printMessage(char* result, unsigned maxPrint, size_t threadid, float timeStamp, const char* message, const void* arg0, const void* arg1,
|
||||
const void* arg2, const void* arg3, const void* arg4, float* lastTimeStamp = NULL)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
float delta = 0;
|
||||
if (lastTimeStamp)
|
||||
{
|
||||
delta = (*lastTimeStamp - timeStamp) * 1000.0f;
|
||||
*lastTimeStamp = timeStamp;
|
||||
}
|
||||
|
||||
int printed = snprintf(result, maxPrint - 1, "%.5f %.1f ms %x:", timeStamp, delta, unsigned(threadid));
|
||||
if (static_cast<int>(reinterpret_cast<long>(arg0)) == FLOAT_MARKER)
|
||||
snprintf(result + printed, MAX_LOG_MESSAGE - printed - 1, message, flfBack(arg1), flfBack(arg2), flfBack(arg3), flfBack(arg4));
|
||||
else
|
||||
snprintf(result + printed, MAX_LOG_MESSAGE - printed - 1, message, arg0, arg1, arg2, arg3, arg4);
|
||||
#pragma warning(pop)
|
||||
return;
|
||||
}
|
||||
|
||||
void printMessageFormatted(char* result, unsigned maxPrint, unsigned threadid, float timeStamp, const char* message, float* lastTimeStamp = NULL)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
float delta = 0;
|
||||
if (lastTimeStamp)
|
||||
{
|
||||
delta = (*lastTimeStamp - timeStamp) * 1000.0f;
|
||||
*lastTimeStamp = timeStamp;
|
||||
}
|
||||
|
||||
int printed = snprintf(result, maxPrint - 1, "%.5f %.1f ms %u:", timeStamp, delta, threadid);
|
||||
strncat(result, message, MAX_LOG_MESSAGE - printed - 1);
|
||||
#pragma warning(pop)
|
||||
return;
|
||||
}
|
||||
|
||||
void printMessage(
|
||||
char* result, unsigned maxPrint, size_t threadid, float timeStamp, const char* message, const char* sarg, float* lastTimeStamp = NULL)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
float delta = 0;
|
||||
if (lastTimeStamp)
|
||||
{
|
||||
delta = (*lastTimeStamp - timeStamp) * 1000.0f;
|
||||
*lastTimeStamp = timeStamp;
|
||||
}
|
||||
int printed = snprintf(result, maxPrint - 1, "%.5f %.1f ms %x:", timeStamp, delta, unsigned(threadid));
|
||||
snprintf(result + printed, MAX_LOG_MESSAGE - printed - 1, message, sarg);
|
||||
#pragma warning(pop)
|
||||
return;
|
||||
}
|
||||
|
||||
struct BinaryLogDumper
|
||||
{
|
||||
void* context;
|
||||
Detail::BinaryLogFunc callback;
|
||||
|
||||
boost::mutex currentMutex;
|
||||
LogEntry* current;
|
||||
long counter;
|
||||
long history;
|
||||
|
||||
boost::mutex writeQueueMutex;
|
||||
boost::condition_variable writeQueueHasData;
|
||||
std::deque<LogEntry*> writeQueue;
|
||||
|
||||
boost::thread writeThread;
|
||||
|
||||
BinaryLogDumper(void* context, Detail::BinaryLogFunc callback)
|
||||
: context(context)
|
||||
, callback(callback)
|
||||
, current(0)
|
||||
, counter(0)
|
||||
, history(8192)
|
||||
{
|
||||
boost::thread t(boost::bind(&BinaryLogDumper::writeWorker, this));
|
||||
writeThread.swap(t);
|
||||
}
|
||||
|
||||
static void append(std::vector<char>& s, const void* data, size_t size)
|
||||
{
|
||||
s.insert(s.end(), static_cast<const char*>(data), static_cast<const char*>(data) + size);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void append(std::vector<char>& s, const T& v)
|
||||
{
|
||||
append(s, &v, sizeof(v));
|
||||
}
|
||||
|
||||
void writeWorker()
|
||||
{
|
||||
std::set<const void*> strings;
|
||||
|
||||
std::vector<char> data;
|
||||
|
||||
while (true)
|
||||
{
|
||||
LogEntry* entry = NULL;
|
||||
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(writeQueueMutex);
|
||||
|
||||
if (writeQueue.empty())
|
||||
writeQueueHasData.wait(lock);
|
||||
|
||||
if (!writeQueue.empty())
|
||||
{
|
||||
entry = writeQueue.front();
|
||||
writeQueue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
if (entry)
|
||||
{
|
||||
int header[2] = {};
|
||||
|
||||
data.resize(sizeof(header));
|
||||
|
||||
// append string data
|
||||
for (int i = 0; i < history; ++i)
|
||||
{
|
||||
std::pair<std::set<const void*>::iterator, bool> p = strings.insert(entry[i].message);
|
||||
|
||||
if (p.second)
|
||||
{
|
||||
size_t length = strlen(entry[i].message);
|
||||
|
||||
header[0] += 1;
|
||||
|
||||
append(data, entry[i].message);
|
||||
append(data, length);
|
||||
append(data, entry[i].message, length);
|
||||
}
|
||||
}
|
||||
|
||||
// append entry data
|
||||
header[1] = history;
|
||||
append(data, entry, sizeof(entry[0]) * history);
|
||||
|
||||
// write!
|
||||
memcpy(&data[0], header, sizeof(header));
|
||||
callback(context, &data[0], data.size());
|
||||
|
||||
delete[] entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addEntry(Channel level, const LogEntry& entry)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(currentMutex);
|
||||
|
||||
if (counter == history)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(writeQueueMutex);
|
||||
|
||||
writeQueue.push_back(current);
|
||||
writeQueueHasData.notify_one();
|
||||
|
||||
current = 0;
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
if (!current)
|
||||
{
|
||||
current = new LogEntry[history];
|
||||
}
|
||||
|
||||
current[counter++] = entry;
|
||||
|
||||
if (IsVerboseLogging())
|
||||
{
|
||||
char temp[MAX_LOG_MESSAGE];
|
||||
memset(temp, 0, sizeof(temp));
|
||||
printMessage(temp, MAX_LOG_MESSAGE, entry.threadid, entry.timestamp, entry.message, entry.args.intArgs.arg0, entry.args.intArgs.arg1,
|
||||
entry.args.intArgs.arg2, entry.args.intArgs.arg3, entry.args.intArgs.arg4);
|
||||
printf("[FLog::%d %f (%d)] %s\n", level, entry.timestamp, entry.threadid, temp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static BinaryLogDumper* gDumper;
|
||||
|
||||
void FastLog(Channel level, const char* message, const void* arg0)
|
||||
{
|
||||
FastLog(level, message, arg0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void FastLog(Channel level, const char* message, const void* arg0, const void* arg1, const void* arg2)
|
||||
{
|
||||
FastLog(level, message, arg0, arg1, arg2, 0, 0);
|
||||
}
|
||||
|
||||
void FastLogFormatted(Channel level, const char* message, ...)
|
||||
{
|
||||
va_list pArgList;
|
||||
va_start(pArgList, message);
|
||||
char temp[MAX_LOG_MESSAGE];
|
||||
#ifdef WIN32
|
||||
vsnprintf_s(temp, MAX_LOG_MESSAGE, message, pArgList);
|
||||
#else
|
||||
vsnprintf(temp, MAX_LOG_MESSAGE, message, pArgList);
|
||||
#endif
|
||||
va_end(pArgList);
|
||||
|
||||
if (level > LOGCHANNELS)
|
||||
{
|
||||
if (logF)
|
||||
{
|
||||
char temp2[MAX_LOG_MESSAGE];
|
||||
memset(temp2, 0, sizeof(temp2));
|
||||
printMessageFormatted(temp2, MAX_LOG_MESSAGE, (unsigned)GetCurrentThreadId(), (float)timeF(), temp);
|
||||
logF(level - LOGCHANNELS, temp2);
|
||||
return;
|
||||
}
|
||||
else
|
||||
level = 1;
|
||||
}
|
||||
|
||||
LogEntry entry;
|
||||
entry.message = temp;
|
||||
entry.timestamp = (float)timeF();
|
||||
entry.threadid = (unsigned)GetCurrentThreadId();
|
||||
|
||||
Aya::atomic<unsigned int>& counter = g_LogCounters[level - 1];
|
||||
|
||||
unsigned int index = (--counter) % LOG_HISTORY;
|
||||
|
||||
g_FastLog[level - 1][index] = entry;
|
||||
|
||||
if (gDumper)
|
||||
gDumper->addEntry(level, entry);
|
||||
}
|
||||
|
||||
void FastLog(Channel level, const char* message, const void* arg0, const void* arg1, const void* arg2, const void* arg3, const void* arg4)
|
||||
{
|
||||
if (level > LOGCHANNELS)
|
||||
{
|
||||
if (logF)
|
||||
{
|
||||
char temp[MAX_LOG_MESSAGE];
|
||||
memset(temp, 0, sizeof(temp));
|
||||
printMessage(temp, MAX_LOG_MESSAGE, GetCurrentThreadId(), (float)timeF(), message, arg0, arg1, arg2, arg3, arg4);
|
||||
logF(level - LOGCHANNELS, temp);
|
||||
return;
|
||||
}
|
||||
else
|
||||
level = 1;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
char temp[MAX_LOG_MESSAGE];
|
||||
memset(temp, 0, sizeof(temp));
|
||||
printMessage(temp, MAX_LOG_MESSAGE, GetCurrentThreadId(), (float)timeF(), message, arg0, arg1, arg2, arg3, arg4);
|
||||
// printf("%s\n", temp);
|
||||
#endif
|
||||
|
||||
LogEntry entry;
|
||||
entry.message = message;
|
||||
entry.args.intArgs.arg0 = arg0;
|
||||
entry.args.intArgs.arg1 = arg1;
|
||||
entry.args.intArgs.arg2 = arg2;
|
||||
entry.args.intArgs.arg3 = arg3;
|
||||
entry.args.intArgs.arg4 = arg4;
|
||||
entry.timestamp = (float)timeF();
|
||||
entry.threadid = GetCurrentThreadId();
|
||||
|
||||
Aya::atomic<unsigned int>& counter = g_LogCounters[level - 1];
|
||||
|
||||
unsigned int index = (--counter) % LOG_HISTORY;
|
||||
|
||||
g_FastLog[level - 1][index] = entry;
|
||||
|
||||
if (gDumper)
|
||||
gDumper->addEntry(level, entry);
|
||||
}
|
||||
|
||||
void Init(TimeFunc tF)
|
||||
{
|
||||
timeF = tF;
|
||||
}
|
||||
|
||||
void SetExternalLogFunc(ExternalLogFunc lF)
|
||||
{
|
||||
logF = lF;
|
||||
}
|
||||
|
||||
|
||||
void FastLogF(Channel channel, const char* message, float arg1, float arg2, float arg3, float arg4)
|
||||
{
|
||||
FastLog(channel, message, (void*)FLOAT_MARKER, flf(arg1), flf(arg2), flf(arg3), flf(arg4));
|
||||
}
|
||||
|
||||
|
||||
void FastLogS(Channel level, const char* message, const char* sarg, int sargsize)
|
||||
{
|
||||
if (level > LOGCHANNELS)
|
||||
{
|
||||
if (logF)
|
||||
{
|
||||
char temp[MAX_LOG_MESSAGE];
|
||||
memset(temp, 0, sizeof(temp));
|
||||
printMessage(temp, MAX_LOG_MESSAGE, GetCurrentThreadId(), (float)timeF(), message, sarg);
|
||||
logF(level - LOGCHANNELS, temp);
|
||||
return;
|
||||
}
|
||||
else
|
||||
level = 1;
|
||||
}
|
||||
|
||||
LogEntry entry;
|
||||
entry.message = message;
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
if (sarg != NULL)
|
||||
{
|
||||
const char* start = &sarg[std::max(0, sargsize - (int)(sizeof(entry.args.sarg) - 1))];
|
||||
strncpy(entry.args.sarg, start, sizeof(entry.args.sarg));
|
||||
entry.args.sarg[sizeof(entry.args.sarg) - 1] = '\0';
|
||||
}
|
||||
else
|
||||
strncpy(entry.args.sarg, "NULL", sizeof(entry.args.sarg));
|
||||
#pragma warning(pop)
|
||||
|
||||
entry.timestamp = (float)timeF();
|
||||
entry.threadid = GetCurrentThreadId();
|
||||
|
||||
Aya::atomic<unsigned int>& counter = g_LogCounters[level - 1];
|
||||
|
||||
unsigned int index = (--counter) % LOG_HISTORY;
|
||||
|
||||
g_FastLog[level - 1][index] = entry;
|
||||
|
||||
if (gDumper)
|
||||
gDumper->addEntry(level, entry);
|
||||
}
|
||||
|
||||
void FastLogS(Channel level, const char* message, const char* sarg)
|
||||
{
|
||||
FastLogS(level, message, sarg, sarg ? (int)strlen(sarg) : 0);
|
||||
}
|
||||
|
||||
void FastLogS(Channel level, const char* message, const std::string& sarg)
|
||||
{
|
||||
FastLogS(level, message, sarg.c_str(), (int)sarg.size());
|
||||
}
|
||||
|
||||
void WriteFastLogDump(const char* fileName, int numEntries)
|
||||
{
|
||||
std::ofstream logFileStream(fileName);
|
||||
if (logFileStream.fail())
|
||||
return;
|
||||
|
||||
if (numEntries > LOG_HISTORY)
|
||||
numEntries = LOG_HISTORY;
|
||||
|
||||
for (int ii = 0; ii < LOGCHANNELS; ++ii)
|
||||
{
|
||||
float lastTimeStamp = 0;
|
||||
logFileStream << "Level " << ii << "\n";
|
||||
|
||||
if (!g_LogCounters[ii])
|
||||
continue;
|
||||
|
||||
unsigned int currentIndex = g_LogCounters[ii] % LOG_HISTORY;
|
||||
|
||||
for (int currentCount = 1; currentCount <= numEntries; ++currentCount)
|
||||
{
|
||||
if (currentIndex >= LOG_HISTORY)
|
||||
currentIndex = 0;
|
||||
|
||||
LogEntry* entry = &(g_FastLog[ii][currentIndex++]);
|
||||
if (!entry || !entry->message)
|
||||
continue;
|
||||
|
||||
char buffer[MAX_LOG_MESSAGE];
|
||||
if (strstr(entry->message, "%s"))
|
||||
printMessage(buffer, MAX_LOG_MESSAGE, entry->threadid, entry->timestamp, entry->message, entry->args.sarg, &lastTimeStamp);
|
||||
else
|
||||
printMessage(buffer, MAX_LOG_MESSAGE, entry->threadid, entry->timestamp, entry->message, entry->args.intArgs.arg0,
|
||||
entry->args.intArgs.arg1, entry->args.intArgs.arg2, entry->args.intArgs.arg3, entry->args.intArgs.arg4, &lastTimeStamp);
|
||||
|
||||
logFileStream << buffer << "\n";
|
||||
}
|
||||
|
||||
logFileStream.flush();
|
||||
}
|
||||
|
||||
logFileStream.close();
|
||||
}
|
||||
|
||||
unsigned short GetNumSynchronizedVariable()
|
||||
{
|
||||
unsigned short count = 0;
|
||||
for (Variables::iterator iter = gVariables->begin(); iter != gVariables->end(); iter++)
|
||||
{
|
||||
if ((iter->second->getType() & FASTVARTYPE_SYNC) > 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void ResetSynchronizedVariablesState()
|
||||
{
|
||||
for (Variables::iterator iter = gVariables->begin(); iter != gVariables->end(); iter++)
|
||||
{
|
||||
if ((iter->second->getType() & FASTVARTYPE_SYNC) > 0)
|
||||
{
|
||||
iter->second->setIsSync(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GetFastLogCounter(Channel channel)
|
||||
{
|
||||
if (channel > LOGCHANNELS)
|
||||
channel = 1;
|
||||
|
||||
return g_LogCounters[channel - 1];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void RegisterVariable(const char* varName, T* value, bool** isSync, FastVarType fastVarType)
|
||||
{
|
||||
if (gVariables == NULL)
|
||||
gVariables = new Variables();
|
||||
|
||||
if (gUnknownVariables == NULL)
|
||||
gUnknownVariables = new UnknownVariables();
|
||||
|
||||
|
||||
std::string name = varName;
|
||||
|
||||
assert(gVariables->find(name) == gVariables->end());
|
||||
|
||||
ValueGetSet<T>* varValue = new ValueGetSet<T>(value, fastVarType);
|
||||
gVariables->insert(Variables::value_type(name, varValue));
|
||||
if (isSync != NULL)
|
||||
{
|
||||
*isSync = varValue->getIsSyncAddr();
|
||||
}
|
||||
|
||||
UnknownVariables::iterator itUnknown = gUnknownVariables->find(varName);
|
||||
if (itUnknown != gUnknownVariables->end())
|
||||
{
|
||||
Variables::iterator itVars = gVariables->find(name);
|
||||
itVars->second->set(itUnknown->second, fastVarType);
|
||||
|
||||
gUnknownVariables->erase(itUnknown);
|
||||
}
|
||||
}
|
||||
|
||||
int RegisterLogGroup(const char* groupName, Channel* groupVar, bool** isSync, FastVarType fastVarType)
|
||||
{
|
||||
RegisterVariable(groupName, groupVar, isSync, fastVarType);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int RegisterString(const char* groupName, std::string* groupVar, bool** isSync, FastVarType fastVarType)
|
||||
{
|
||||
RegisterVariable(groupName, groupVar, isSync, fastVarType);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int RegisterInt(const char* groupName, int* groupVar, bool** isSync, FastVarType fastVarType)
|
||||
{
|
||||
RegisterVariable(groupName, groupVar, isSync, fastVarType);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int RegisterFlag(const char* flagName, bool* flagVar, bool** isSync, FastVarType fastVarType)
|
||||
{
|
||||
RegisterVariable(flagName, flagVar, isSync, fastVarType);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void visitVariable(Variables::value_type pair, VariableVisitor visitor, void* context)
|
||||
{
|
||||
visitor(pair.first.c_str(), pair.second->get(), context);
|
||||
}
|
||||
|
||||
void ForEachVariable(VariableVisitor visitor, void* context, FastVarType flagType)
|
||||
{
|
||||
if (flagType == FASTVARTYPE_ANY)
|
||||
{
|
||||
std::for_each(gVariables->begin(), gVariables->end(), boost::bind(visitVariable, _1, visitor, context));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Variables::iterator iter = gVariables->begin(); iter != gVariables->end(); iter++)
|
||||
{
|
||||
if ((iter->second->getType() & flagType) > 0)
|
||||
{
|
||||
visitor(iter->first.c_str(), iter->second->get(), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SetValue(const std::string& name, const std::string& value, FastVarType fastVarType, bool loadedFromServer)
|
||||
{
|
||||
if (!gVariables)
|
||||
return false;
|
||||
|
||||
if (!gUnknownVariables)
|
||||
gUnknownVariables = new UnknownVariables();
|
||||
|
||||
bool result = false;
|
||||
|
||||
Variables::iterator it = gVariables->find(name);
|
||||
if (it != gVariables->end())
|
||||
{
|
||||
it->second->set(value, fastVarType);
|
||||
if (loadedFromServer)
|
||||
{
|
||||
it->second->setIsSync(true);
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
(*gUnknownVariables)[name] = value;
|
||||
}
|
||||
|
||||
FASTLOGS(FLog::FastLogValueChanged, "Setting variable %s", name);
|
||||
FASTLOGS(FLog::FastLogValueChanged, "...to value %s", value);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GetValue(const std::string& name, std::string& value, bool alsoCheckUnknown)
|
||||
{
|
||||
Variables::iterator it = gVariables->find(name);
|
||||
if (it != gVariables->end())
|
||||
{
|
||||
value = it->second->get();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alsoCheckUnknown)
|
||||
{
|
||||
UnknownVariables::const_iterator unknownIt = gUnknownVariables->find(name);
|
||||
if (unknownIt != gUnknownVariables->end())
|
||||
{
|
||||
value = unknownIt->second;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SetValueFromServer(const std::string& name, const std::string& value)
|
||||
{
|
||||
return SetValue(name, value, FASTVARTYPE_SYNC, true);
|
||||
}
|
||||
|
||||
double NowFast()
|
||||
{
|
||||
return timeF();
|
||||
}
|
||||
|
||||
namespace Detail
|
||||
{
|
||||
void SetBinaryLog(void* context, BinaryLogFunc callback)
|
||||
{
|
||||
assert(!gDumper);
|
||||
|
||||
BinaryLogDumper* dumper = new BinaryLogDumper(context, callback);
|
||||
gDumper = dumper;
|
||||
}
|
||||
} // namespace Detail
|
||||
} // namespace FLog
|
||||
462
engine/core/src/FastLog.hpp
Normal file
462
engine/core/src/FastLog.hpp
Normal file
@@ -0,0 +1,462 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#define FASTLOG(group, message) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLog(group, message "", 0); \
|
||||
} while (0)
|
||||
#define FASTLOG1(group, message, arg1) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLog(group, message "", reinterpret_cast<const void*>(arg1)); \
|
||||
} while (0)
|
||||
#define FASTLOG2(group, message, arg1, arg2) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLog(group, message "", reinterpret_cast<const void*>(arg1), reinterpret_cast<const void*>(arg2), 0); \
|
||||
} while (0)
|
||||
#define FASTLOG3(group, message, arg1, arg2, arg3) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLog( \
|
||||
group, message "", reinterpret_cast<const void*>(arg1), reinterpret_cast<const void*>(arg2), reinterpret_cast<const void*>(arg3)); \
|
||||
} while (0)
|
||||
#define FASTLOG4(group, message, arg1, arg2, arg3, arg4) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLog(group, message "", reinterpret_cast<const void*>(arg1), reinterpret_cast<const void*>(arg2), \
|
||||
reinterpret_cast<const void*>(arg3), reinterpret_cast<const void*>(arg4), 0); \
|
||||
} while (0)
|
||||
#define FASTLOG5(group, message, arg1, arg2, arg3, arg4, arg5) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLog(group, message "", reinterpret_cast<const void*>(arg1), reinterpret_cast<const void*>(arg2), \
|
||||
reinterpret_cast<const void*>(arg3), reinterpret_cast<const void*>(arg4), reinterpret_cast<const void*>(arg5)); \
|
||||
} while (0)
|
||||
|
||||
#define FASTLOGS(group, message, sarg) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLogS(group, message "", sarg); \
|
||||
} while (0)
|
||||
#define FASTLOG1F(group, message, arg1) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLogF(group, message "", arg1); \
|
||||
} while (0)
|
||||
#define FASTLOG2F(group, message, arg1, arg2) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLogF(group, message "", arg1, arg2); \
|
||||
} while (0)
|
||||
#define FASTLOG3F(group, message, arg1, arg2, arg3) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLogF(group, message "", arg1, arg2, arg3); \
|
||||
} while (0)
|
||||
#define FASTLOG4F(group, message, arg1, arg2, arg3, arg4) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLogF(group, message "", arg1, arg2, arg3, arg4); \
|
||||
} while (0)
|
||||
|
||||
|
||||
#define FASTLOGNOFILTER(channel, message) FLog::FastLog(channel, message "", 0)
|
||||
#define FASTLOGNOFILTER2(channel, message, arg1, arg2) \
|
||||
FLog::FastLog(channel, message "", reinterpret_cast<const void*>(arg1), reinterpret_cast<const void*>(arg2), 0)
|
||||
|
||||
#define FASTLOGF(group, message, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (group) \
|
||||
FLog::FastLogFormatted(group, message "", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define LOG_HISTORY 2048 // Has to be a power of two. If changing, modify debugger entries below
|
||||
|
||||
#define LOGGROUP(group) \
|
||||
namespace FLog \
|
||||
{ \
|
||||
extern Channel group; \
|
||||
}
|
||||
#define LOGVARIABLE(group, defaulton) \
|
||||
namespace FLog \
|
||||
{ \
|
||||
Channel group = defaulton; \
|
||||
int group##Initer = RegisterLogGroup(#group, &group, NULL, FASTVARTYPE_STATIC); \
|
||||
}
|
||||
|
||||
#define DYNAMIC_LOGGROUP(group) \
|
||||
namespace DFLog \
|
||||
{ \
|
||||
extern FLog::Channel group; \
|
||||
}
|
||||
#define DYNAMIC_LOGVARIABLE(group, defaulton) \
|
||||
namespace DFLog \
|
||||
{ \
|
||||
FLog::Channel group = defaulton; \
|
||||
int group##Initer = FLog::RegisterLogGroup(#group, &group, NULL, FASTVARTYPE_DYNAMIC); \
|
||||
}
|
||||
|
||||
#define SYNCHRONIZED_LOGGROUP(group) \
|
||||
namespace SFLog \
|
||||
{ \
|
||||
extern FLog::Channel get##group(); \
|
||||
}
|
||||
#define SYNCHRONIZED_LOGVARIABLE(group, defaulton) \
|
||||
namespace SFLog \
|
||||
{ \
|
||||
FLog::Channel group = defaulton; \
|
||||
bool* group##IsSync; \
|
||||
int group##Initer = FLog::RegisterLogGroup(#group, &group, &group##IsSync, FASTVARTYPE_SYNC); \
|
||||
FLog::Channel get##group() \
|
||||
{ \
|
||||
assert(*group##IsSync == true); \
|
||||
return group; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define FASTSTRING(var) \
|
||||
namespace FString \
|
||||
{ \
|
||||
extern std::string var; \
|
||||
}
|
||||
#define FASTSTRINGVARIABLE(var, defaultvalue) \
|
||||
namespace FString \
|
||||
{ \
|
||||
std::string var = defaultvalue; \
|
||||
int var##Initer = FLog::RegisterString(#var, &var, NULL, FASTVARTYPE_STATIC); \
|
||||
}
|
||||
|
||||
#define FASTFLAG(var) \
|
||||
namespace FFlag \
|
||||
{ \
|
||||
extern bool var; \
|
||||
}
|
||||
#define FASTFLAGVARIABLE(var, defaultvalue) \
|
||||
FASTSTRINGVARIABLE(PlaceFilter_##var, "") \
|
||||
namespace FFlag \
|
||||
{ \
|
||||
bool var = defaultvalue; \
|
||||
int var##Initer = FLog::RegisterFlag(#var, &var, NULL, FASTVARTYPE_STATIC); \
|
||||
}
|
||||
|
||||
#define DYNAMIC_FASTFLAG(var) \
|
||||
namespace DFFlag \
|
||||
{ \
|
||||
extern bool var; \
|
||||
}
|
||||
#define DYNAMIC_FASTFLAGVARIABLE(var, defaultvalue) \
|
||||
FASTSTRINGVARIABLE(PlaceFilter_##var, "") \
|
||||
namespace DFFlag \
|
||||
{ \
|
||||
bool var = defaultvalue; \
|
||||
int var##Initer = FLog::RegisterFlag(#var, &var, NULL, FASTVARTYPE_DYNAMIC); \
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
#if defined(__i386__)
|
||||
#define AYA_DEBUG_BREAK() \
|
||||
{ \
|
||||
__asm__ __volatile__("int $3"); \
|
||||
} \
|
||||
(void)0
|
||||
#else
|
||||
#define AYA_DEBUG_BREAK() \
|
||||
{ \
|
||||
::abort(); \
|
||||
} \
|
||||
(void)0
|
||||
#endif
|
||||
#else
|
||||
#define AYA_DEBUG_BREAK() \
|
||||
{ \
|
||||
__debugbreak(); \
|
||||
} \
|
||||
(void)0
|
||||
#endif
|
||||
|
||||
#if defined(_DEBUG) || defined(_NOOPT) || defined(AYA_TEST_BUILD)
|
||||
#define VALIDATE_SYNCHRONIZED_FLAG(var) \
|
||||
{ \
|
||||
if (!var) \
|
||||
AYA_DEBUG_BREAK(); \
|
||||
} \
|
||||
((void)0)
|
||||
#else
|
||||
#define VALIDATE_SYNCHRONIZED_FLAG(var) ((void)0)
|
||||
#endif
|
||||
|
||||
#define SYNCHRONIZED_FASTFLAG(var) \
|
||||
namespace SFFlag \
|
||||
{ \
|
||||
extern bool get##var(); \
|
||||
}
|
||||
#define SYNCHRONIZED_FASTFLAGVARIABLE(var, defaultvalue) \
|
||||
FASTSTRINGVARIABLE(PlaceFilter_##var, "") \
|
||||
namespace SFFlag \
|
||||
{ \
|
||||
bool var = defaultvalue; \
|
||||
bool* var##IsSync; \
|
||||
int var##Initer = FLog::RegisterFlag(#var, &var, &var##IsSync, FASTVARTYPE_SYNC); \
|
||||
bool get##var() \
|
||||
{ \
|
||||
VALIDATE_SYNCHRONIZED_FLAG(*var##IsSync); \
|
||||
return var; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define FASTINT(var) \
|
||||
namespace FInt \
|
||||
{ \
|
||||
extern int var; \
|
||||
}
|
||||
#define FASTINTVARIABLE(var, defaultvalue) \
|
||||
FASTSTRINGVARIABLE(PlaceFilter_##var, "") \
|
||||
namespace FInt \
|
||||
{ \
|
||||
int var = defaultvalue; \
|
||||
int var##Initer = FLog::RegisterInt(#var, &var, NULL, FASTVARTYPE_STATIC); \
|
||||
}
|
||||
|
||||
#define DYNAMIC_FASTINT(var) \
|
||||
namespace DFInt \
|
||||
{ \
|
||||
extern int var; \
|
||||
}
|
||||
#define DYNAMIC_FASTINTVARIABLE(var, defaultvalue) \
|
||||
FASTSTRINGVARIABLE(PlaceFilter_##var, "") \
|
||||
namespace DFInt \
|
||||
{ \
|
||||
int var = defaultvalue; \
|
||||
int var##Initer = FLog::RegisterInt(#var, &var, NULL, FASTVARTYPE_DYNAMIC); \
|
||||
}
|
||||
|
||||
#define SYNCHRONIZED_FASTINT(var) \
|
||||
namespace SFInt \
|
||||
{ \
|
||||
extern int get##var(); \
|
||||
}
|
||||
#define SYNCHRONIZED_FASTINTVARIABLE(var, defaultvalue) \
|
||||
FASTSTRINGVARIABLE(PlaceFilter_##var, "") \
|
||||
namespace SFInt \
|
||||
{ \
|
||||
int var = defaultvalue; \
|
||||
bool* var##IsSync; \
|
||||
int var##Initer = FLog::RegisterInt(#var, &var, &var##IsSync, FASTVARTYPE_SYNC); \
|
||||
int get##var() \
|
||||
{ \
|
||||
VALIDATE_SYNCHRONIZED_FLAG(*var##IsSync); \
|
||||
return var; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DYNAMIC_FASTSTRING(var) \
|
||||
namespace DFString \
|
||||
{ \
|
||||
extern std::string var; \
|
||||
}
|
||||
#define DYNAMIC_FASTSTRINGVARIABLE(var, defaultvalue) \
|
||||
namespace DFString \
|
||||
{ \
|
||||
std::string var = defaultvalue; \
|
||||
int var##Initer = FLog::RegisterString(#var, &var, NULL, FASTVARTYPE_DYNAMIC); \
|
||||
}
|
||||
|
||||
#define SYNCHRONIZED_FASTSTRING(var) \
|
||||
namespace SFString \
|
||||
{ \
|
||||
extern std::string get##var(); \
|
||||
}
|
||||
#define SYNCHRONIZED_FASTSTRINGVARIABLE(var, defaultvalue) \
|
||||
namespace SFString \
|
||||
{ \
|
||||
std::string var = defaultvalue; \
|
||||
bool* var##IsSync; \
|
||||
int var##Initer = FLog::RegisterString(#var, &var, &var##IsSync, FASTVARTYPE_SYNC); \
|
||||
std::string get##var() \
|
||||
{ \
|
||||
VALIDATE_SYNCHRONIZED_FLAG(*var##IsSync); \
|
||||
return var; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ABTEST_NEWUSERS(var) \
|
||||
namespace ABNewUsers \
|
||||
{ \
|
||||
extern int var; \
|
||||
}
|
||||
#define ABTEST_NEWUSERS_VARIABLE(var) \
|
||||
namespace ABNewUsers \
|
||||
{ \
|
||||
int var = 0; \
|
||||
int var##Initer = FLog::RegisterInt(#var, &var, NULL, FASTVARTYPE_AB_NEWUSERS); \
|
||||
}
|
||||
|
||||
#define ABTEST_NEWSTUDIOUSERS(var) \
|
||||
namespace ABNewStudioUsers \
|
||||
{ \
|
||||
extern int var; \
|
||||
}
|
||||
#define ABTEST_NEWSTUDIOUSERS_VARIABLE(var) \
|
||||
namespace ABNewStudioUsers \
|
||||
{ \
|
||||
int var = 0; \
|
||||
int var##Initer = FLog::RegisterInt(#var, &var, NULL, FASTVARTYPE_AB_NEWSTUDIOUSERS); \
|
||||
}
|
||||
|
||||
#define ABTEST_ALLUSERS(var) \
|
||||
namespace ABAllUsers \
|
||||
{ \
|
||||
extern int var; \
|
||||
}
|
||||
#define ABTEST_ALLUSERS_VARIABLE(var) \
|
||||
namespace ABAllUsers \
|
||||
{ \
|
||||
int var = 0; \
|
||||
int var##Initer = FLog::RegisterInt(#var, &var, NULL, FASTVARTYPE_AB_ALLUSERS); \
|
||||
}
|
||||
|
||||
#define LOGCHANNELS 5
|
||||
|
||||
#define FILECHANNEL(id) (LOGCHANNELS + id)
|
||||
|
||||
enum ABTestVariation
|
||||
{
|
||||
Control = 0,
|
||||
Variation1 = 1,
|
||||
Variation2 = 2,
|
||||
Variation3 = 3,
|
||||
Variation4 = 4,
|
||||
Variation5 = 5
|
||||
};
|
||||
|
||||
enum FastVarType
|
||||
{
|
||||
FASTVARTYPE_STATIC = 0,
|
||||
FASTVARTYPE_DYNAMIC = 1,
|
||||
FASTVARTYPE_SYNC = 2,
|
||||
FASTVARTYPE_AB_NEWUSERS = 4,
|
||||
FASTVARTYPE_AB_NEWSTUDIOUSERS = 8,
|
||||
FASTVARTYPE_AB_ALLUSERS = 16,
|
||||
FASTVARTYPE_ANY = 1 + 2 + 4 + 8 + 16
|
||||
|
||||
};
|
||||
|
||||
namespace FLog
|
||||
{
|
||||
|
||||
// Mini guide:
|
||||
|
||||
// ---------------------------------
|
||||
// Using logs
|
||||
// ---------------------------------
|
||||
// Use FASTLOG macros _ONLY_
|
||||
|
||||
|
||||
// Examples:
|
||||
|
||||
// Declare a group, then log using this group:
|
||||
// LOGGROUP(Test);
|
||||
// Important(!): Add group to v8dataModel/FastLogSettings.cpp
|
||||
|
||||
// Just message - only constant strings are allowed (no generated strings), use arguments instead
|
||||
// FASTLOG(FLog::Test, "Cannot initialize graphics engine");
|
||||
|
||||
// Message with arguments like numbers and pointers
|
||||
// FASTLOG2(FLog::Test, "Sound systems %p has %u effects", system, effectcount);
|
||||
|
||||
// Message with floating-point arguments - has to use flf() function:
|
||||
// FASTLOG1F(FLog::Test, "Average block size %f", avesize);
|
||||
|
||||
// Message with string (works for both char* and std::strings) - only one string is allowed
|
||||
// FASTLOGS(FLog::Test, "Object name: %s", name);
|
||||
|
||||
// Channel 1: Non-chatty / important events (Game started, loaded UI script) -- more permanent messages
|
||||
// Channel 2: Per frame data
|
||||
// Channel 3-5: User defined / used for debugging / more temporary
|
||||
|
||||
// ---------------------------------
|
||||
// Looking at the logs
|
||||
// ---------------------------------
|
||||
// Helpful debugger entries - select and mouse over:
|
||||
// Channel 1 - {,,Log}g_FastLog[0]+({,,Log}g_LogCounters[0]%2048)
|
||||
// Channel 2 - {,,Log}g_FastLog[1]+({,,Log}g_LogCounters[1]%2048)
|
||||
// Channel 3 - {,,Log}g_FastLog[2]+({,,Log}g_LogCounters[2]%2048)
|
||||
// Channel 4 - {,,Log}g_FastLog[3]+({,,Log}g_LogCounters[3]%2048)
|
||||
// Channel 5 - {,,Log}g_FastLog[4]+({,,Log}g_LogCounters[4]%2048)
|
||||
|
||||
typedef unsigned char Channel;
|
||||
|
||||
// Make "too many actual parameters for macro" a error
|
||||
#pragma warning(error : 4002)
|
||||
|
||||
typedef double (*TimeFunc)();
|
||||
typedef void (*ExternalLogFunc)(Channel channel, const char* message);
|
||||
void Init(TimeFunc timeF);
|
||||
void SetExternalLogFunc(ExternalLogFunc logF);
|
||||
|
||||
void FastLog(Channel channel, const char* message, const void* arg0);
|
||||
void FastLog(Channel channel, const char* message, const void* arg0, const void* arg1, const void* arg2);
|
||||
void FastLog(Channel channel, const char* message, const void* arg0, const void* arg1, const void* arg2, const void* arg3, const void* arg4);
|
||||
void FastLogS(Channel channel, const char* message, const char* sarg);
|
||||
void FastLogS(Channel channel, const char* message, const std::string& sarg);
|
||||
void FastLogF(Channel channel, const char* message, float arg1, float arg2 = 0.0f, float arg3 = 0.0f, float arg4 = 0.0f);
|
||||
void FastLogFormatted(Channel channel, const char* message, ...);
|
||||
|
||||
int GetFastLogCounter(Channel channel);
|
||||
|
||||
int RegisterLogGroup(const char* groupName, Channel* groupVar, bool** isSync = NULL, FastVarType fastVarType = FASTVARTYPE_STATIC);
|
||||
int RegisterFlag(const char* flagName, bool* flagVar, bool** isSync = NULL, FastVarType fastVarType = FASTVARTYPE_STATIC);
|
||||
int RegisterInt(const char* flagName, int* flagVar, bool** isSync = NULL, FastVarType fastVarType = FASTVARTYPE_STATIC);
|
||||
int RegisterString(const char* flagName, std::string* flagVar, bool** isSync = NULL, FastVarType fastVarType = FASTVARTYPE_STATIC);
|
||||
|
||||
typedef void (*VariableVisitor)(const std::string& name, const std::string& value, void* context);
|
||||
void ForEachVariable(VariableVisitor visitor, void* context, FastVarType flagType);
|
||||
|
||||
bool SetValue(const std::string& name, const std::string& value, FastVarType fastVarType = FASTVARTYPE_ANY, bool loadedFromServer = false);
|
||||
bool GetValue(const std::string& name, std::string& value, bool alsoCheckUnknownFlags = false);
|
||||
bool SetValueFromServer(const std::string& name, const std::string& value);
|
||||
|
||||
double NowFast();
|
||||
void WriteFastLogDump(const char* fileName, int numEntries);
|
||||
|
||||
unsigned short GetNumSynchronizedVariable();
|
||||
void ResetSynchronizedVariablesState();
|
||||
|
||||
namespace Detail
|
||||
{
|
||||
typedef void (*BinaryLogFunc)(void* context, const void* data, size_t size);
|
||||
void SetBinaryLog(void* context, BinaryLogFunc callback);
|
||||
} // namespace Detail
|
||||
} // namespace FLog
|
||||
|
||||
LOGGROUP(Legacy)
|
||||
|
||||
namespace FLog
|
||||
{
|
||||
const Channel Always = 1;
|
||||
const Channel Error = 1;
|
||||
const Channel Warning = 1;
|
||||
}; // namespace FLog
|
||||
144
engine/core/src/FastLogStream.cpp
Normal file
144
engine/core/src/FastLogStream.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include "FastLogStream.hpp"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <unistd.h>
|
||||
#elif defined(__linux) || defined(__APPLE__)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Ws2tcpip.h>
|
||||
|
||||
typedef SOCKET socket_t;
|
||||
|
||||
#define SOCKET_VALID(s) (s != INVALID_SOCKET)
|
||||
#define SOCKET_CLOSE(s) closesocket(s)
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
typedef int socket_t;
|
||||
|
||||
#define SOCKET_VALID(s) (s >= 0)
|
||||
#define SOCKET_CLOSE(s) close(s)
|
||||
|
||||
static int fopen_s(FILE** pFile, const char* filename, const char* mode)
|
||||
{
|
||||
(*pFile) = fopen(filename, mode);
|
||||
return (*pFile) != NULL ? 0 : -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
void LogStreamFileWrite(void* context, const void* data, size_t size)
|
||||
{
|
||||
fwrite(data, 1, size, static_cast<FILE*>(context));
|
||||
}
|
||||
|
||||
void LogStreamSocketWrite(void* context, const void* data, size_t size)
|
||||
{
|
||||
// socket_t for windows and socklen_t for Apple
|
||||
send(static_cast<socket_t>(reinterpret_cast<long>(context)), static_cast<const char*>(data), (int)size, 0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace FLog
|
||||
{
|
||||
bool SetLogStreamFile(const char* path)
|
||||
{
|
||||
FILE* file;
|
||||
|
||||
if (fopen_s(&file, path, "wb") == 0)
|
||||
{
|
||||
printf("LogStream: Dumping to file %s\n", path);
|
||||
|
||||
Detail::SetBinaryLog(file, LogStreamFileWrite);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SetLogStreamNetworkClient(const char* address, unsigned int port)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(1, 1), &wsaData);
|
||||
#endif
|
||||
|
||||
socket_t sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
#if defined(__linux) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
if (SOCKET_VALID(sock))
|
||||
{
|
||||
sockaddr_in sin = {};
|
||||
sin.sin_family = AF_INET;
|
||||
InetPton(AF_INET, address, &sin.sin_addr.s_addr);
|
||||
sin.sin_port = htons(port);
|
||||
|
||||
printf("LogStream: Connecting to %s:%d\n", address, port);
|
||||
|
||||
if (connect(sock, reinterpret_cast<sockaddr*>(&sin), sizeof(sin)) >= 0)
|
||||
{
|
||||
printf("LogStream: Dumping to socket\n");
|
||||
|
||||
Detail::SetBinaryLog(reinterpret_cast<void*>(sock), LogStreamSocketWrite);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SOCKET_CLOSE(sock);
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SetLogStreamNetworkServer(unsigned int port)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(1, 1), &wsaData);
|
||||
#endif
|
||||
|
||||
#ifndef __APPLE__
|
||||
socket_t sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
if (SOCKET_VALID(sock))
|
||||
{
|
||||
sockaddr_in sin = {};
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
sin.sin_port = htons(port);
|
||||
|
||||
if (bind(sock, reinterpret_cast<sockaddr*>(&sin), sizeof(sin)) >= 0)
|
||||
{
|
||||
if (listen(sock, 1) >= 0)
|
||||
{
|
||||
printf("LogStream: Listening for incoming connection on port %d\n", port);
|
||||
|
||||
socket_t client = accept(sock, NULL, NULL);
|
||||
|
||||
if (SOCKET_VALID(client))
|
||||
{
|
||||
printf("LogStream: Dumping to socket\n");
|
||||
|
||||
Detail::SetBinaryLog(reinterpret_cast<void*>(client), LogStreamSocketWrite);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SOCKET_CLOSE(sock);
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace FLog
|
||||
12
engine/core/src/FastLogStream.hpp
Normal file
12
engine/core/src/FastLogStream.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "FastLog.hpp"
|
||||
|
||||
namespace FLog
|
||||
{
|
||||
|
||||
bool SetLogStreamFile(const char* path);
|
||||
bool SetLogStreamNetworkClient(const char* address, unsigned int port);
|
||||
bool SetLogStreamNetworkServer(unsigned int port);
|
||||
|
||||
} // namespace FLog
|
||||
70
engine/core/src/GlobalVectorItem.hpp
Normal file
70
engine/core/src/GlobalVectorItem.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
struct GlobalVectorItemBase
|
||||
{
|
||||
bool valid;
|
||||
GlobalVectorItemBase()
|
||||
: valid(false){};
|
||||
};
|
||||
|
||||
// this class manages a sparse static array.
|
||||
// allocate one of these "smartptr-like" classes to get the next available entry in the list.
|
||||
// T should derive from GlobalVectorItemBase
|
||||
template<class T>
|
||||
class GlobalVectorItemPtr
|
||||
{
|
||||
T* p;
|
||||
T* newp;
|
||||
|
||||
public:
|
||||
GlobalVectorItemPtr(T* list, size_t count)
|
||||
: p(0)
|
||||
, newp(0)
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i, ++list)
|
||||
{
|
||||
if (!list->valid)
|
||||
{
|
||||
p = list;
|
||||
p->valid = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// out of space. just allocate one.
|
||||
|
||||
newp = new T();
|
||||
p = newp;
|
||||
}
|
||||
|
||||
~GlobalVectorItemPtr()
|
||||
{
|
||||
if (newp)
|
||||
{
|
||||
delete newp;
|
||||
newp = 0;
|
||||
p = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// free slot.
|
||||
p->valid = false;
|
||||
p = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
T* operator->()
|
||||
{
|
||||
return this->p;
|
||||
}
|
||||
|
||||
T* get()
|
||||
{
|
||||
return this->p;
|
||||
}
|
||||
};
|
||||
} // namespace Aya
|
||||
65
engine/core/src/HardwareInfo.cpp
Normal file
65
engine/core/src/HardwareInfo.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "boost/lexical_cast.hpp"
|
||||
#include "geekinfo.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
|
||||
|
||||
unsigned char CPUCount(unsigned int* TotAvailLogical, unsigned int* TotAvailCore, unsigned int* PhysicalNum)
|
||||
{
|
||||
// this is using GeekInfo library. EL.
|
||||
unsigned char StatusFlag(0);
|
||||
std::string val = systemMetric(kSystemMetricCPULogicalCount);
|
||||
try
|
||||
{
|
||||
*TotAvailLogical = boost::lexical_cast<unsigned int>(val.c_str());
|
||||
}
|
||||
catch (boost::bad_lexical_cast&)
|
||||
{
|
||||
StatusFlag = 1;
|
||||
}
|
||||
val = systemMetric(kSystemMetricCPUCoreCount);
|
||||
try
|
||||
{
|
||||
*TotAvailCore = boost::lexical_cast<unsigned int>(val.c_str());
|
||||
}
|
||||
catch (boost::bad_lexical_cast&)
|
||||
{
|
||||
StatusFlag = 1;
|
||||
}
|
||||
val = systemMetric(kSystemMetricCPUPhysicalCount);
|
||||
try
|
||||
{
|
||||
*PhysicalNum = boost::lexical_cast<unsigned int>(val.c_str());
|
||||
}
|
||||
catch (boost::bad_lexical_cast&)
|
||||
{
|
||||
StatusFlag = 1;
|
||||
}
|
||||
|
||||
return StatusFlag; // 0 == success
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
#if 0
|
||||
#include <iostream>
|
||||
|
||||
// UNIT TEST (by eric l.)!
|
||||
|
||||
// to test this, use this line in the console (with gcc).
|
||||
//
|
||||
// g++ -IGeekInfo/src/geekinfo-2.1.4/include -I../boost_1_38_0/include HardwareInfo.cpp -o runHardareInfo -Wl,-syslibroot,/Developer/SDKs/MacOSX10.5.sdk -mmacosx-version-min=10.5 -arch i386 GeekInfo/src/geekinfo-2.1.4/build.x86_32/libgeekinfo.a -LGeekInfo/src/geekinfo-2.1.4/build.x86_32/lib -framework Carbon -framework IOKit -framework Cocoa -framework WebKit -framework AGL -framework OpenGL
|
||||
//
|
||||
//
|
||||
|
||||
int main() {
|
||||
std::cout << systemMetricName(kSystemMetricCPU) << ": " << systemMetric(kSystemMetricCPU) << std::endl;
|
||||
unsigned int l, c, p;
|
||||
NewCPUCount( &l, &c, &p );
|
||||
fprintf(stderr,"logical: %d Core: %d Physical: %d\n", l, c, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
6
engine/core/src/HardwareInfo.hpp
Normal file
6
engine/core/src/HardwareInfo.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
unsigned char CPUCount(unsigned int* TotAvailLogical, unsigned int* TotAvailCore, unsigned int* PhysicalNum);
|
||||
|
||||
}
|
||||
198
engine/core/src/ImGui.cpp
Normal file
198
engine/core/src/ImGui.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "ImGui.hpp"
|
||||
#include "DataModel/ContentProvider.hpp"
|
||||
|
||||
#define INPUT_CONFIG
|
||||
|
||||
struct InputState
|
||||
{
|
||||
int mouseX, mouseY, mouseWheel;
|
||||
bool mouseButton[4];
|
||||
};
|
||||
|
||||
InputState gImGuiInputState;
|
||||
bool gImGuiContextCreated = false;
|
||||
bool gImGuiFontReady = false;
|
||||
|
||||
#ifdef AYAIMGUI
|
||||
namespace Aya
|
||||
{
|
||||
namespace ImGui
|
||||
{
|
||||
|
||||
bool isInitialized()
|
||||
{
|
||||
return gImGuiContextCreated && gImGuiFontReady;
|
||||
}
|
||||
|
||||
void onFrame()
|
||||
{
|
||||
if (!gImGuiContextCreated || !gImGuiFontReady)
|
||||
return;
|
||||
|
||||
::ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void gpuInit()
|
||||
{
|
||||
::IMGUI_CHECKVERSION();
|
||||
::ImGui::CreateContext();
|
||||
::ImPlot::CreateContext();
|
||||
|
||||
// ImGui config
|
||||
ImGuiIO& imio = ::ImGui::GetIO();
|
||||
imio.DeltaTime = 1.0 / 60.0f;
|
||||
imio.DisplaySize = ImVec2(800.0f, 600.0f); // will be updated each frame
|
||||
// imio.Fonts->AddFontFromFileTTF(ContentProvider::getAssetFile("fonts/SourceSansPro-Regular.ttf").c_str(), 16.0f);
|
||||
// imio.ConfigFlags |= INPUT_CONFIG;
|
||||
// imio.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
|
||||
// ImGui style
|
||||
ImGuiStyle& style = ::ImGui::GetStyle();
|
||||
ImVec4* colors = style.Colors;
|
||||
|
||||
#define COL(r, g, b) ::ImGui::ColorConvertU32ToFloat4(IM_COL32(r, g, b, 255))
|
||||
#define COLA(r, g, b, a) ::ImGui::ColorConvertU32ToFloat4(IM_COL32(r, g, b, a))
|
||||
|
||||
// === Base ===
|
||||
colors[ImGuiCol_WindowBg] = COL(247, 247, 247);
|
||||
colors[ImGuiCol_ChildBg] = COL(255, 255, 255);
|
||||
colors[ImGuiCol_PopupBg] = COL(255, 255, 255);
|
||||
colors[ImGuiCol_Border] = COL(204, 204, 204);
|
||||
colors[ImGuiCol_FrameBg] = COL(242, 242, 242);
|
||||
colors[ImGuiCol_FrameBgHovered] = COL(230, 230, 255);
|
||||
colors[ImGuiCol_FrameBgActive] = COL(217, 217, 255);
|
||||
colors[ImGuiCol_Text] = COL(26, 26, 26);
|
||||
colors[ImGuiCol_TextDisabled] = COL(102, 102, 102);
|
||||
|
||||
// === Title Bar ===
|
||||
colors[ImGuiCol_TitleBg] = COL(133, 64, 255); // #8540FF
|
||||
colors[ImGuiCol_TitleBgActive] = COL(133, 64, 255);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = COL(191, 166, 255);
|
||||
|
||||
// === Buttons & Headers ===
|
||||
colors[ImGuiCol_Button] = COL(235, 235, 235);
|
||||
colors[ImGuiCol_ButtonHovered] = COL(224, 224, 255);
|
||||
colors[ImGuiCol_ButtonActive] = COL(209, 209, 255);
|
||||
colors[ImGuiCol_Header] = COL(230, 217, 255);
|
||||
colors[ImGuiCol_HeaderHovered] = COL(217, 204, 255);
|
||||
colors[ImGuiCol_HeaderActive] = COL(191, 166, 255);
|
||||
colors[ImGuiCol_TextSelectedBg] = COLA(133, 64, 255, 89);
|
||||
|
||||
// === Scrollbar ===
|
||||
colors[ImGuiCol_ScrollbarBg] = COL(247, 247, 247);
|
||||
colors[ImGuiCol_ScrollbarGrab] = COL(204, 204, 204);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = COL(179, 179, 230);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = COL(153, 153, 217);
|
||||
|
||||
style.WindowRounding = 6.0f;
|
||||
style.ChildRounding = 4.0f;
|
||||
style.FrameRounding = 2.0f;
|
||||
style.PopupRounding = 2.0f;
|
||||
style.GrabRounding = 1.0f;
|
||||
style.TabRounding = 2.0f;
|
||||
|
||||
#undef COL
|
||||
#undef COLA
|
||||
|
||||
gImGuiContextCreated = true;
|
||||
}
|
||||
|
||||
void gpuShutdown()
|
||||
{
|
||||
::ImPlot::DestroyContext();
|
||||
::ImGui::DestroyContext();
|
||||
gImGuiContextCreated = false;
|
||||
gImGuiFontReady = false;
|
||||
}
|
||||
|
||||
bool isCapturingMouseInput()
|
||||
{
|
||||
if (!gImGuiContextCreated)
|
||||
return false;
|
||||
|
||||
return ::ImGui::GetIO().WantCaptureMouse;
|
||||
}
|
||||
|
||||
bool handleMouse(unsigned int flags, int mouseX, int mouseY, int mouseWheel, int mouseButton)
|
||||
{
|
||||
if (!gImGuiContextCreated)
|
||||
return false;
|
||||
|
||||
if (flags & Flag_MouseMove)
|
||||
{
|
||||
gImGuiInputState.mouseX = mouseX;
|
||||
gImGuiInputState.mouseY = mouseY;
|
||||
}
|
||||
|
||||
if (flags & Flag_MouseWheel)
|
||||
gImGuiInputState.mouseWheel = mouseWheel;
|
||||
|
||||
if (flags & Flag_MouseDown)
|
||||
gImGuiInputState.mouseButton[mouseButton] = true;
|
||||
|
||||
if (flags & Flag_MouseUp)
|
||||
gImGuiInputState.mouseButton[mouseButton] = false;
|
||||
|
||||
ImGuiIO& imio = ::ImGui::GetIO();
|
||||
imio.MousePos = ImVec2(std::max(gImGuiInputState.mouseX, 0), std::max(gImGuiInputState.mouseY, 0));
|
||||
imio.MouseWheel = gImGuiInputState.mouseWheel;
|
||||
imio.MouseDown[0] = gImGuiInputState.mouseButton[0];
|
||||
imio.MouseDown[1] = gImGuiInputState.mouseButton[1];
|
||||
imio.MouseDown[2] = gImGuiInputState.mouseButton[2];
|
||||
|
||||
return isCapturingMouseInput();
|
||||
}
|
||||
|
||||
void getFont(unsigned char** pixels, int* w, int* h)
|
||||
{
|
||||
::ImGui::GetIO().Fonts->GetTexDataAsRGBA32(pixels, w, h);
|
||||
// The font texture data has been produced; the renderer should upload it and then
|
||||
// set the GPU texture id into ImGui (or we can treat this flag as "font ready").
|
||||
gImGuiFontReady = true;
|
||||
}
|
||||
|
||||
void render(Renderer* renderer, unsigned int width, unsigned int height)
|
||||
{
|
||||
// Need a valid context to call Render, and also ensure font/texture has been produced/uploaded.
|
||||
if (!gImGuiContextCreated)
|
||||
return;
|
||||
if (!gImGuiFontReady)
|
||||
return;
|
||||
|
||||
ImGuiIO& imio = ::ImGui::GetIO();
|
||||
imio.DisplaySize = ImVec2((float)width, (float)height);
|
||||
|
||||
::ImGui::Render();
|
||||
|
||||
renderer->draw(::ImGui::GetDrawData());
|
||||
|
||||
gImGuiInputState.mouseWheel = 0;
|
||||
}
|
||||
} // namespace ImGui
|
||||
} // namespace Aya
|
||||
#else
|
||||
namespace Aya
|
||||
{
|
||||
namespace ImGui
|
||||
{
|
||||
|
||||
void onFrame() {}
|
||||
|
||||
void gpuInit() {}
|
||||
|
||||
void gpuShutdown() {}
|
||||
|
||||
bool isCapturingMouseInput()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handleMouse(unsigned int flags, int mouseX, int mouseY, int mouseWheel, int mouseButton)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void render(Renderer* renderer, unsigned int width, unsigned int height) {}
|
||||
} // namespace ImGui
|
||||
} // namespace Aya
|
||||
#endif
|
||||
40
engine/core/src/ImGui.hpp
Normal file
40
engine/core/src/ImGui.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
#include <implot.h>
|
||||
|
||||
#define AYAIMGUI
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace ImGui
|
||||
{
|
||||
|
||||
void onFrame();
|
||||
|
||||
enum Flags
|
||||
{
|
||||
Flag_MouseMove = 1 << 0,
|
||||
Flag_MouseWheel = 1 << 1,
|
||||
Flag_MouseDown = 1 << 2,
|
||||
Flag_MouseUp = 1 << 3,
|
||||
};
|
||||
|
||||
void gpuInit();
|
||||
void gpuShutdown();
|
||||
|
||||
bool isCapturingMouseInput();
|
||||
bool handleMouse(unsigned int flags, int mouseX, int mouseY, int mouseWheel, int mouseButton);
|
||||
bool isInitialized();
|
||||
|
||||
struct Renderer
|
||||
{
|
||||
virtual ~Renderer() {}
|
||||
|
||||
virtual void draw(ImDrawData* data) = 0;
|
||||
};
|
||||
|
||||
void getFont(unsigned char** pixels, int* w, int* h);
|
||||
void render(Renderer* renderer, unsigned int width, unsigned int height);
|
||||
} // namespace ImGui
|
||||
} // namespace Aya
|
||||
164
engine/core/src/Log.cpp
Normal file
164
engine/core/src/Log.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "Log.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#define sprints sprintf_s
|
||||
#define wcstombs wcstombs_s
|
||||
#else
|
||||
#endif
|
||||
|
||||
#include "boost/date_time/posix_time/posix_time.hpp"
|
||||
#include "boost/date_time/gregorian/gregorian.hpp"
|
||||
#include "boost/date_time/gregorian/greg_year.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
#include "time.hpp"
|
||||
|
||||
ILogProvider* Log::provider = NULL;
|
||||
|
||||
Log::Severity Log::aggregateWorstSeverity = Log::Information;
|
||||
|
||||
void Log::timeStamp(std::ofstream& stream, bool includeDate)
|
||||
{
|
||||
/*
|
||||
boost::posix_time::ptime stime(boost::posix_time::second_clock::local_time());
|
||||
char s[256];
|
||||
|
||||
if (includeDate)
|
||||
{
|
||||
boost::gregorian::date date(stime.date());
|
||||
snprintf(s, ARRAYSIZE(s), "%02u.%02u.%u ", date.day().as_number(), date.month().as_number(), (unsigned short)date.year());
|
||||
stream << s;
|
||||
}
|
||||
boost::posix_time::time_duration dur(stime.time_of_day());
|
||||
snprintf(s, ARRAYSIZE(s), "%02u:%02u:%02u.%03u (%03.07f)", (unsigned int)dur.hours(), (unsigned int)dur.minutes(), (unsigned int)dur.seconds(),
|
||||
(unsigned short)dur.total_milliseconds(), Time::nowFastSec()); stream << s; stream.flush();
|
||||
|
||||
// d9mz - why is it still trying to do log shit after i disabled it?
|
||||
*/
|
||||
}
|
||||
|
||||
Log::Log(const char* logFile, const char* name)
|
||||
: stream(logFile)
|
||||
, logFile(logFile)
|
||||
, worstSeverity(Information)
|
||||
, name(name)
|
||||
{
|
||||
Log::timeStamp(stream, true);
|
||||
stream << "Log \"" << name << "\"\n";
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
Log::~Log(void)
|
||||
{
|
||||
Log::timeStamp(stream, true);
|
||||
stream << "End Log\n";
|
||||
}
|
||||
|
||||
void Log::setLogProvider(ILogProvider* provider)
|
||||
{
|
||||
Log::provider = provider;
|
||||
}
|
||||
|
||||
void Log::writeEntry(Severity severity, const wchar_t* message)
|
||||
{
|
||||
// Convert to a char*
|
||||
size_t origsize = wcslen(message) + 1;
|
||||
const size_t newsize = origsize + 100;
|
||||
size_t convertedChars = 0;
|
||||
char* nstring = new char[newsize];
|
||||
#ifdef _WIN32
|
||||
wcstombs_s(&convertedChars, nstring, origsize, message, _TRUNCATE);
|
||||
#else
|
||||
convertedChars = wcstombs(nstring, message, origsize);
|
||||
#endif
|
||||
if (convertedChars >= origsize - 1)
|
||||
nstring[origsize - 1] = '\0';
|
||||
writeEntry(severity, nstring);
|
||||
delete[] nstring;
|
||||
}
|
||||
|
||||
|
||||
void Log::writeEntry(Severity severity, const char* message)
|
||||
{
|
||||
/*
|
||||
static const char* error = " Error: ";
|
||||
static const char* warning = " Warning: ";
|
||||
static const char* information = " ";
|
||||
Log::timeStamp(stream, false);
|
||||
switch (severity)
|
||||
{
|
||||
case Log::Error:
|
||||
stream << error;
|
||||
break;
|
||||
case Log::Warning:
|
||||
stream << warning;
|
||||
break;
|
||||
case Log::Information:
|
||||
stream << information;
|
||||
break;
|
||||
}
|
||||
stream << message;
|
||||
stream << '\n';
|
||||
stream.flush();
|
||||
*/
|
||||
}
|
||||
|
||||
std::string Log::formatMem(uint64_t bytes)
|
||||
{
|
||||
char* suffix[] = {"B", "KB", "MB", "GB", "TB"};
|
||||
char length = sizeof(suffix) / sizeof(suffix[0]);
|
||||
|
||||
int i = 0;
|
||||
double dblBytes = bytes;
|
||||
|
||||
if (bytes > 1024)
|
||||
{
|
||||
for (i = 0; (bytes / 1024) > 0 && i < length - 1; i++, bytes /= 1024)
|
||||
dblBytes = bytes / 1024.0;
|
||||
}
|
||||
|
||||
static char output[200];
|
||||
sprintf(output, "%.02lf %s", dblBytes, suffix[i]);
|
||||
|
||||
return std::string(output);
|
||||
}
|
||||
|
||||
std::string Log::formatTime(double time)
|
||||
{
|
||||
char buffer[64];
|
||||
if (time == 0.0)
|
||||
snprintf(buffer, ARRAYSIZE(buffer), "0s");
|
||||
if (time < 0.0)
|
||||
snprintf(buffer, ARRAYSIZE(buffer), "%.3gs", time);
|
||||
else if (time >= 0.1)
|
||||
snprintf(buffer, ARRAYSIZE(buffer), "%.3gs", time);
|
||||
else
|
||||
snprintf(buffer, ARRAYSIZE(buffer), "%.3gms", time * 1000.0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Log::timeStamp(bool includeDate)
|
||||
{
|
||||
Log::timeStamp(Log::currentStream(), includeDate);
|
||||
}
|
||||
|
||||
LOGVARIABLE(Crash, 1)
|
||||
LOGVARIABLE(HangDetection, 0)
|
||||
LOGVARIABLE(ContentProviderCleanup, 0)
|
||||
LOGVARIABLE(ISteppedLifetime, 0)
|
||||
LOGVARIABLE(MutexLifetime, 0)
|
||||
LOGVARIABLE(TaskScheduler, 0)
|
||||
LOGVARIABLE(TaskSchedulerInit, 0)
|
||||
LOGVARIABLE(TaskSchedulerRun, 0)
|
||||
LOGVARIABLE(TaskSchedulerFindJob, 0)
|
||||
LOGVARIABLE(TaskSchedulerSteps, 0)
|
||||
LOGVARIABLE(Asserts, 0)
|
||||
LOGVARIABLE(FWLifetime, 0)
|
||||
LOGVARIABLE(FWUpdate, 0)
|
||||
LOGVARIABLE(KernelStats, 0)
|
||||
|
||||
void initBaseLog() {}
|
||||
78
engine/core/src/Log.hpp
Normal file
78
engine/core/src/Log.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
#ifndef _A1177B91C54B40259B36DD55E5DF7726
|
||||
#define _A1177B91C54B40259B36DD55E5DF7726
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Log;
|
||||
|
||||
// Returns a Log instance. Multithreaded apps should return a different
|
||||
// instance for each thread, so that Scope objects don't interact with each other
|
||||
class AyaInterface ILogProvider
|
||||
{
|
||||
public:
|
||||
virtual Log* provideLog() = 0;
|
||||
};
|
||||
|
||||
class Log
|
||||
{
|
||||
const std::string name;
|
||||
|
||||
public:
|
||||
// return a string representing the amount of memory
|
||||
static std::string formatMem(uint64_t bytes);
|
||||
static std::string formatTime(double time);
|
||||
|
||||
enum Severity
|
||||
{
|
||||
Information = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
};
|
||||
static Severity aggregateWorstSeverity; // The worst severity level reported by any Log
|
||||
Severity worstSeverity; // The worst severity level reported by this Log
|
||||
|
||||
void writeEntry(Severity severity, const char* message);
|
||||
void writeEntry(Severity severity, const wchar_t* message);
|
||||
void timeStamp(bool includeDate);
|
||||
|
||||
static void setLogProvider(ILogProvider* provider);
|
||||
|
||||
Log(const char* logFile, const char* name);
|
||||
virtual ~Log(void);
|
||||
|
||||
const std::string logFile;
|
||||
|
||||
static inline Log* current()
|
||||
{
|
||||
return provider ? provider->provideLog() : NULL;
|
||||
}
|
||||
|
||||
static void timeStamp(std::ofstream& stream, bool includeDate);
|
||||
|
||||
private:
|
||||
std::ofstream stream;
|
||||
|
||||
|
||||
static ILogProvider* provider;
|
||||
static inline std::ofstream& currentStream()
|
||||
{
|
||||
AYAASSERT(provider->provideLog() != NULL);
|
||||
return provider->provideLog()->stream;
|
||||
}
|
||||
|
||||
friend class Entry;
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
#endif
|
||||
158
engine/core/src/MathUtil.cpp
Normal file
158
engine/core/src/MathUtil.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "Debug.hpp"
|
||||
#include "MathUtil.hpp"
|
||||
#include <float.h>
|
||||
#include <cmath>
|
||||
#ifdef AYA_OS_WINDOWS
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
int GetMappedDOF(int dof)
|
||||
{
|
||||
if (dof <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (dof <= 30)
|
||||
{
|
||||
return dof;
|
||||
}
|
||||
else if (dof < 40)
|
||||
{
|
||||
return 31;
|
||||
}
|
||||
else if (dof < 50)
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
else if (dof < 60)
|
||||
{
|
||||
return 33;
|
||||
}
|
||||
else if (dof < 80)
|
||||
{
|
||||
return 34;
|
||||
}
|
||||
else if (dof < 100)
|
||||
{
|
||||
return 35;
|
||||
}
|
||||
else if (dof < 120)
|
||||
{
|
||||
return 36;
|
||||
}
|
||||
else if (dof == 120)
|
||||
{
|
||||
return 37;
|
||||
}
|
||||
return 38;
|
||||
};
|
||||
|
||||
const double DOFs[][5] = {
|
||||
{1, 6.314, 12.71, 63.66, 636.6},
|
||||
{2, 2.92, 4.303, 9.925, 31.6},
|
||||
{3, 2.353, 3.182, 5.841, 12.92},
|
||||
{4, 2.132, 2.776, 4.604, 8.61},
|
||||
{5, 2.015, 2.571, 4.032, 6.869},
|
||||
{6, 1.943, 2.447, 3.707, 5.959},
|
||||
{7, 1.895, 2.365, 3.499, 5.408},
|
||||
{8, 1.86, 2.306, 3.355, 5.041},
|
||||
{9, 1.833, 2.262, 3.25, 4.781},
|
||||
{10, 1.812, 2.228, 3.169, 4.587},
|
||||
{11, 1.796, 2.201, 3.106, 4.437},
|
||||
{12, 1.782, 2.179, 3.055, 4.318},
|
||||
{13, 1.771, 2.16, 3.012, 4.221},
|
||||
{14, 1.761, 2.145, 2.977, 4.14},
|
||||
{15, 1.753, 2.131, 2.947, 4.073},
|
||||
{16, 1.746, 2.12, 2.921, 4.015},
|
||||
{17, 1.74, 2.11, 2.898, 3.965},
|
||||
{18, 1.734, 2.101, 2.878, 3.922},
|
||||
{19, 1.729, 2.093, 2.861, 3.883},
|
||||
{20, 1.725, 2.086, 2.845, 3.85},
|
||||
{21, 1.721, 2.08, 2.831, 3.819},
|
||||
{22, 1.717, 2.074, 2.819, 3.792},
|
||||
{23, 1.714, 2.069, 2.807, 3.767},
|
||||
{24, 1.711, 2.064, 2.797, 3.745},
|
||||
{25, 1.708, 2.06, 2.787, 3.725},
|
||||
{26, 1.706, 2.056, 2.779, 3.707},
|
||||
{27, 1.703, 2.052, 2.771, 3.69},
|
||||
{28, 1.701, 2.048, 2.763, 3.674},
|
||||
{29, 1.699, 2.045, 2.756, 3.659},
|
||||
{30, 1.697, 2.042, 2.75, 3.646},
|
||||
{40, 1.684, 2.021, 2.704, 3.551},
|
||||
{50, 1.676, 2.009, 2.678, 3.496},
|
||||
{60, 1.671, 2, 2.66, 3.46},
|
||||
{80, 1.664, 1.99, 2.639, 3.416},
|
||||
{100, 1.66, 1.984, 2.626, 3.39},
|
||||
{120, 1.658, 1.98, 2.617, 3.373},
|
||||
{121, 1.645, 1.96, 2.576, 3.291},
|
||||
};
|
||||
|
||||
|
||||
inline double TCritical(unsigned dof, Confidence conf)
|
||||
{
|
||||
if (dof == 0)
|
||||
return DBL_MAX;
|
||||
|
||||
int mappedDOF = GetMappedDOF(dof);
|
||||
|
||||
AYAASSERT(conf < 4);
|
||||
AYAASSERT(mappedDOF < ARRAYSIZE(DOFs));
|
||||
|
||||
return DOFs[mappedDOF][conf + 1];
|
||||
}
|
||||
|
||||
|
||||
double IsValueOutlier(double value, unsigned int count, double average, double std, Confidence conf)
|
||||
{
|
||||
double tCritical = TCritical(count - 1, conf);
|
||||
double tScore = fabs((value - average) / std);
|
||||
return tScore > tCritical;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GetConfidenceInterval(double average, double variance, Confidence conf, double* minV, double* maxV)
|
||||
{
|
||||
double std = sqrt(variance);
|
||||
|
||||
switch (conf)
|
||||
{
|
||||
case C90:
|
||||
if (minV)
|
||||
*minV = average - std * 1.644;
|
||||
|
||||
if (maxV)
|
||||
*maxV = average + std * 1.644;
|
||||
break;
|
||||
case C95:
|
||||
if (minV)
|
||||
*minV = average - std * 2;
|
||||
|
||||
if (maxV)
|
||||
*maxV = average + std * 2;
|
||||
break;
|
||||
|
||||
case C99:
|
||||
if (minV)
|
||||
*minV = average - std * 3;
|
||||
|
||||
if (maxV)
|
||||
*maxV = average + std * 3;
|
||||
break;
|
||||
|
||||
case C99p9:
|
||||
if (minV)
|
||||
*minV = average - std * 4;
|
||||
|
||||
if (maxV)
|
||||
*maxV = average + std * 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
20
engine/core/src/MathUtil.hpp
Normal file
20
engine/core/src/MathUtil.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
enum Confidence
|
||||
{
|
||||
C90,
|
||||
C95,
|
||||
C99,
|
||||
C99p9,
|
||||
ConfidenceMax
|
||||
};
|
||||
|
||||
double IsValueOutlier(double value, unsigned int count, double average, double std, Confidence conf);
|
||||
|
||||
void GetConfidenceInterval(double average, double variance, Confidence conf, double* minV, double* maxV);
|
||||
|
||||
} // namespace Aya
|
||||
111
engine/core/src/Memory.cpp
Normal file
111
engine/core/src/Memory.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
|
||||
// This prevent inclusion of winsock.h in Windows.h, which prevents windows redifinition errors
|
||||
// Look at winsock2.h for details, winsock2.h is #included from boost.hpp & other places.
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCKAPI_
|
||||
#endif
|
||||
|
||||
#include "Memory.hpp"
|
||||
|
||||
|
||||
bool Aya::roblox_allocator::crashOnAllocationFailure = true;
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
std::vector<size_t*> poolAvailabilityList;
|
||||
std::vector<releaseFunc> poolReleaseMemoryFuncList;
|
||||
std::vector<size_t*> poolAllocationList;
|
||||
|
||||
#ifdef AYA_MEMORY_SCALABLE_MALLOC
|
||||
char* roblox_allocator::malloc(const size_type size)
|
||||
{
|
||||
return reinterpret_cast<char*>(scalable_malloc(size > 0 ? size : 1));
|
||||
}
|
||||
void roblox_allocator::free(char* const block)
|
||||
{
|
||||
return scalable_free(block);
|
||||
}
|
||||
char* roblox_allocator::realloc(char* ptr, size_t nsize)
|
||||
{
|
||||
return reinterpret_cast<char*>(scalable_realloc(ptr, nsize));
|
||||
}
|
||||
#else
|
||||
char* roblox_allocator::malloc(const size_type size)
|
||||
{
|
||||
char* result = (char*)std::malloc(size > 0 ? size : 1);
|
||||
if (!result && size && crashOnAllocationFailure)
|
||||
AYACRASH(); // We want a nice fat crash here so that the process quits and we can log it
|
||||
return result;
|
||||
}
|
||||
void roblox_allocator::free(char* const block)
|
||||
{
|
||||
return std::free(block);
|
||||
}
|
||||
char* roblox_allocator::realloc(char* ptr, size_t nsize)
|
||||
{
|
||||
char* result = (char*)std::realloc(ptr, nsize);
|
||||
if (!result && nsize && crashOnAllocationFailure)
|
||||
AYACRASH(); // We want a nice fat crash here so that the process quits and we can log it
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
} // namespace Aya
|
||||
|
||||
// Globally override new/delete operators
|
||||
#ifdef AYA_MEMORY_SCALABLE_MALLOC
|
||||
|
||||
void* operator new(size_t size)
|
||||
{
|
||||
// No use of std::new_handler because it cannot be done in portable
|
||||
// and thread-safe way
|
||||
//
|
||||
// We throw std::bad_alloc() when scalable_malloc returns NULL
|
||||
//(we return NULL if it is a no-throw implementation)
|
||||
if (void* ptr = scalable_malloc(size > 0 ? size : 1))
|
||||
return ptr;
|
||||
if (Aya::roblox_allocator::crashOnAllocationFailure)
|
||||
AYACRASH(); // We want a nice fat crash here so that the process quits and we can log it
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
void* operator new[](size_t size)
|
||||
{
|
||||
return operator new(size);
|
||||
}
|
||||
|
||||
void* operator new(size_t size, const std::nothrow_t&) throw()
|
||||
{
|
||||
if (void* ptr = scalable_malloc(size > 0 ? size : 1))
|
||||
return ptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* operator new[](size_t size, const std::nothrow_t&) throw()
|
||||
{
|
||||
return operator new(size, std::nothrow);
|
||||
}
|
||||
|
||||
void operator delete(void* ptr) throw()
|
||||
{
|
||||
if (ptr != 0)
|
||||
scalable_free(ptr);
|
||||
}
|
||||
|
||||
void operator delete[](void* ptr) throw()
|
||||
{
|
||||
operator delete(ptr);
|
||||
}
|
||||
|
||||
void operator delete(void* ptr, const std::nothrow_t&) throw()
|
||||
{
|
||||
if (ptr != 0)
|
||||
scalable_free(ptr);
|
||||
}
|
||||
|
||||
void operator delete[](void* ptr, const std::nothrow_t&) throw()
|
||||
{
|
||||
operator delete(ptr, std::nothrow);
|
||||
}
|
||||
|
||||
#endif
|
||||
286
engine/core/src/Memory.hpp
Normal file
286
engine/core/src/Memory.hpp
Normal file
@@ -0,0 +1,286 @@
|
||||
|
||||
|
||||
#ifndef _0632EE02291848e49902EAA033B8C2EA
|
||||
#define _0632EE02291848e49902EAA033B8C2EA
|
||||
|
||||
#include "boost/pool/singleton_pool.hpp"
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include <assert.h>
|
||||
#include "Debug.hpp"
|
||||
#include "atomic.hpp"
|
||||
#include <vector>
|
||||
|
||||
// Note - Interlock Incs, Decs are turned off on count because of possible performance issues
|
||||
// TODO: Benchmark AYA_ALLOCATOR_COUNTS
|
||||
#ifdef _DEBUG
|
||||
#define AYA_ALLOCATOR_COUNTS
|
||||
#define AYA_POOL_ALLOCATION_STATS
|
||||
#endif
|
||||
|
||||
// TODO: Benchmark:
|
||||
#ifndef _DEBUG
|
||||
// Note: Using this option makes it harder to find memory leaks
|
||||
#define AYA_ALLOCATOR_SINGLETON_POOL
|
||||
#endif
|
||||
|
||||
// TODO: Benchmark:
|
||||
// #define AYA_MEMORY_SCALABLE_MALLOC
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
#ifdef AYA_POOL_ALLOCATION_STATS
|
||||
extern std::vector<size_t*> poolAllocationList;
|
||||
#endif
|
||||
typedef bool (*releaseFunc)();
|
||||
extern std::vector<size_t*> poolAvailabilityList;
|
||||
extern std::vector<releaseFunc> poolReleaseMemoryFuncList;
|
||||
|
||||
inline void addToPool(size_t* allocatedSize, size_t* availableSize, size_t size)
|
||||
{
|
||||
if (size > *availableSize)
|
||||
{
|
||||
#ifdef AYA_POOL_ALLOCATION_STATS
|
||||
(*allocatedSize) += (size);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
(*availableSize) -= (size);
|
||||
}
|
||||
}
|
||||
|
||||
inline void removeFromPool(size_t* availableSize, size_t size)
|
||||
{
|
||||
(*availableSize) += (size);
|
||||
}
|
||||
|
||||
// You can use this allocator when using std or boost collections
|
||||
class roblox_allocator
|
||||
{
|
||||
public:
|
||||
static bool crashOnAllocationFailure; // TODO: Put this in more places, including std allocator overrides?
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
static char* malloc(const size_type bytes);
|
||||
static void free(char* const block);
|
||||
static char* realloc(char* ptr, size_t nsize);
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class Allocator
|
||||
{
|
||||
#ifdef AYA_ALLOCATOR_COUNTS
|
||||
static Aya::atomic<int> count;
|
||||
#endif
|
||||
public:
|
||||
static size_t allocatedSize;
|
||||
static size_t availableSize;
|
||||
static bool initialized;
|
||||
|
||||
Allocator()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
#ifdef AYA_POOL_ALLOCATION_STATS
|
||||
poolAllocationList.push_back(&allocatedSize);
|
||||
#endif
|
||||
poolAvailabilityList.push_back(&availableSize);
|
||||
bool (*pReleaseMemory)() = releaseMemory;
|
||||
poolReleaseMemoryFuncList.push_back(pReleaseMemory);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef AYA_ALLOCATOR_SINGLETON_POOL
|
||||
// TODO: Benchmark this allocator vs. other kinds
|
||||
void* operator new(size_t nSize)
|
||||
{
|
||||
assert(nSize == sizeof(T));
|
||||
void* result = boost::singleton_pool<T, sizeof(T), boost::default_user_allocator_malloc_free>::malloc();
|
||||
if (!result)
|
||||
{
|
||||
if (roblox_allocator::crashOnAllocationFailure)
|
||||
AYACRASH(); // We want a nice fat crash here so that the process quits and we can log it
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
#ifdef AYA_ALLOCATOR_COUNTS
|
||||
count++;
|
||||
#endif
|
||||
addToPool(&allocatedSize, &availableSize, nSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
void* operator new(size_t size, void* p)
|
||||
{
|
||||
addToPool(&allocatedSize, &availableSize, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
void operator delete(void*, void*)
|
||||
{
|
||||
removeFromPool(&availableSize, sizeof(T));
|
||||
}
|
||||
|
||||
static bool releaseMemory()
|
||||
{
|
||||
#ifdef AYA_POOL_ALLOCATION_STATS
|
||||
allocatedSize -= availableSize;
|
||||
#endif
|
||||
availableSize = 0;
|
||||
return boost::singleton_pool<T, sizeof(T), boost::default_user_allocator_malloc_free>::release_memory();
|
||||
}
|
||||
|
||||
static bool purgeMemory()
|
||||
{
|
||||
// Be very careful when calling this as this is singleton pool purge
|
||||
#ifdef AYA_POOL_ALLOCATION_STATS
|
||||
allocatedSize = 0;
|
||||
#endif
|
||||
availableSize = 0;
|
||||
return boost::singleton_pool<T, sizeof(T), boost::default_user_allocator_malloc_free>::purge_memory();
|
||||
}
|
||||
|
||||
void operator delete(void* p)
|
||||
{
|
||||
boost::singleton_pool<T, sizeof(T), boost::default_user_allocator_malloc_free>::free(p);
|
||||
#ifdef AYA_ALLOCATOR_COUNTS
|
||||
count--;
|
||||
#endif
|
||||
removeFromPool(&availableSize, sizeof(T));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
#else
|
||||
void* operator new(size_t nSize)
|
||||
{
|
||||
assert(nSize == sizeof(T));
|
||||
void* result = (void*)roblox_allocator::malloc(nSize);
|
||||
if (!result)
|
||||
{
|
||||
if (roblox_allocator::crashOnAllocationFailure)
|
||||
AYACRASH(); // We want a nice fat crash here so that the process quits and we can log it
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
#ifdef AYA_ALLOCATOR_COUNTS
|
||||
count++;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void operator delete(void* p)
|
||||
{
|
||||
roblox_allocator::free((char*)p);
|
||||
#ifdef AYA_ALLOCATOR_COUNTS
|
||||
count--;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* operator new(size_t size, void* p)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
void operator delete(void*, void*)
|
||||
{
|
||||
// placement delete, nothing to do
|
||||
}
|
||||
|
||||
static bool releaseMemory()
|
||||
{
|
||||
// pool not used, nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool purgeMemory()
|
||||
{
|
||||
// pool not used, nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef AYA_ALLOCATOR_COUNTS
|
||||
static long getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
static long getHeapSize()
|
||||
{
|
||||
return sizeof(T) * count;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
template<class T>
|
||||
size_t Allocator<T>::allocatedSize = 0;
|
||||
template<class T>
|
||||
size_t Allocator<T>::availableSize = 0;
|
||||
template<class T>
|
||||
bool Allocator<T>::initialized = false;
|
||||
|
||||
#ifdef AYA_ALLOCATOR_COUNTS
|
||||
template<class T>
|
||||
Aya::atomic<int> Allocator<T>::count;
|
||||
#endif
|
||||
|
||||
// This class is a wrapper for boost::pool<>. It allocates extra memory used by AutoPoolObject
|
||||
// to store a pointer back to the pool.
|
||||
class AutoMemPool
|
||||
{
|
||||
boost::scoped_ptr<boost::pool<>> pool;
|
||||
|
||||
public:
|
||||
// A pool object that auto free itself from the pool it was allocated from
|
||||
// MUST use this with AutoMemPool
|
||||
class Object
|
||||
{
|
||||
public:
|
||||
void* operator new(size_t size, AutoMemPool* pool)
|
||||
{
|
||||
AYAASSERT(((size_t)pool->getRequestedSize()) == size + sizeof(AutoMemPool*));
|
||||
|
||||
void* mem = pool->malloc();
|
||||
*(AutoMemPool**)mem = &(*pool); // store the pool at start of memory block
|
||||
return (char*)mem + sizeof(AutoMemPool*); // skip over the pool
|
||||
}
|
||||
|
||||
void operator delete(void* p, AutoMemPool* pool)
|
||||
{
|
||||
pool->free(p);
|
||||
}
|
||||
|
||||
void operator delete(void* p)
|
||||
{
|
||||
p = (char*)p - sizeof(AutoMemPool*);
|
||||
AutoMemPool* pool = *(AutoMemPool**)p;
|
||||
pool->free(p);
|
||||
}
|
||||
};
|
||||
|
||||
AutoMemPool(int requested_size)
|
||||
{
|
||||
// allocate extra bytes to store pointer to the pool
|
||||
pool.reset(new boost::pool<>(requested_size + sizeof(this)));
|
||||
}
|
||||
|
||||
inline void* malloc()
|
||||
{
|
||||
return pool->malloc();
|
||||
}
|
||||
|
||||
inline void free(void* p)
|
||||
{
|
||||
AYAASSERT(pool->is_from(p));
|
||||
pool->free(p);
|
||||
}
|
||||
|
||||
inline int getRequestedSize()
|
||||
{
|
||||
return int(pool->get_requested_size());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
#endif
|
||||
5
engine/core/src/Nil.hpp
Normal file
5
engine/core/src/Nil.hpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#if defined(__APPLE__)
|
||||
#ifdef nil
|
||||
#undef nil
|
||||
#endif
|
||||
#endif
|
||||
198
engine/core/src/ProcessPerfCounter.cpp
Normal file
198
engine/core/src/ProcessPerfCounter.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "ProcessPerfCounter.hpp"
|
||||
|
||||
#include "Debug.hpp"
|
||||
#include "AyaDbgInfo.hpp"
|
||||
#include <map>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "PdhMsg.h"
|
||||
|
||||
PerfCounter::PerfCounter()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
PDH_STATUS pdhResult = PdhOpenQuery(NULL, 0, &hQuery);
|
||||
#else
|
||||
#warning "MACPORT - NEED TO HANDLE THIS CASE ON THE MAC"
|
||||
#endif
|
||||
}
|
||||
|
||||
CProcessPerfCounter::CProcessPerfCounter()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
init(::GetCurrentProcessId());
|
||||
#else
|
||||
#warning "MACPORT - NEED TO HANDLE THIS CASE ON THE MAC"
|
||||
#endif
|
||||
}
|
||||
CProcessPerfCounter::CProcessPerfCounter(int pid)
|
||||
{
|
||||
init(pid);
|
||||
}
|
||||
void PerfCounter::CollectData()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
PDH_STATUS result = PdhCollectQueryData(hQuery);
|
||||
#else
|
||||
#warning "MACPORT - NEED TO HANDLE THIS CASE ON THE MAC"
|
||||
#endif
|
||||
}
|
||||
|
||||
void PerfCounter::GetData2(HCOUNTER counter, long& result)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
PDH_FMT_COUNTERVALUE stFormattedValue = {0};
|
||||
PDH_STATUS pdhResult = PdhGetFormattedCounterValue(counter, PDH_FMT_LONG, NULL, &stFormattedValue);
|
||||
result = stFormattedValue.longValue;
|
||||
#else
|
||||
#warning "MACPORT - NEED TO HANDLE THIS CASE ON THE MAC"
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void PerfCounter::GetData2(HCOUNTER counter, double& result)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
PDH_FMT_COUNTERVALUE stFormattedValue = {0};
|
||||
PDH_STATUS pdhResult = PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, NULL, &stFormattedValue);
|
||||
result = stFormattedValue.doubleValue;
|
||||
#else
|
||||
#warning "MACPORT - NEED TO HANDLE THIS CASE ON THE MAC"
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
double CProcessPerfCounter::GetProcessCores()
|
||||
{
|
||||
double totalProcessorTime; // 0%-100% of all CPUs
|
||||
GetData2(totalProcessorTimeCounter, totalProcessorTime);
|
||||
double processorTime; // 0%-100% of all processes
|
||||
GetData2(processorTimeCounter, processorTime);
|
||||
|
||||
return totalProcessorTime / 100.0 * processorTime / 100.0 * (double)numCores;
|
||||
}
|
||||
|
||||
void CProcessPerfCounter::init(int pid)
|
||||
{
|
||||
::GetSystemInfo(&systemInfo);
|
||||
numCores = systemInfo.dwNumberOfProcessors;
|
||||
Aya::AyaDbgInfo::s_instance.NumCores = numCores;
|
||||
|
||||
totalProcessorTimeCounter = 0;
|
||||
processorTimeCounter = 0;
|
||||
privateBytesCounter = 0;
|
||||
pageFaultsPerSecondCounter = 0;
|
||||
pageFileBytesCounter = 0;
|
||||
virtualBytesCounter = 0;
|
||||
workingSetPrivateCounter = 0;
|
||||
|
||||
TCHAR* buffer = new TCHAR[100000];
|
||||
TCHAR* instanceName;
|
||||
{
|
||||
DWORD length = 100000;
|
||||
DWORD dummy = 0;
|
||||
PDH_STATUS status = PdhEnumObjectItems(NULL, NULL, "Process", NULL, &dummy, buffer, &length, PERF_DETAIL_EXPERT, 0);
|
||||
AYAASSERT(SUCCEEDED(status) || status == PDH_MORE_DATA);
|
||||
|
||||
std::map<std::string, int> instanceCount;
|
||||
instanceName = buffer;
|
||||
while (instanceName[0] != 0)
|
||||
{
|
||||
std::string name(instanceName);
|
||||
// Insert the item if it doesn't exist:
|
||||
instanceCount.insert(std::pair<std::string, int>(name, 0));
|
||||
|
||||
TCHAR szCounterPath[1024];
|
||||
DWORD dwPathSize = 1024;
|
||||
PDH_COUNTER_PATH_ELEMENTS pe = {0};
|
||||
CQuery hQuery;
|
||||
PDH_STATUS pdhResult = PdhOpenQuery(NULL, 0, &hQuery);
|
||||
pe.szObjectName = "Process";
|
||||
pe.szCounterName = "ID Process";
|
||||
pe.szInstanceName = instanceName;
|
||||
pe.dwInstanceIndex = instanceCount[name]++;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
|
||||
CQuery counter;
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &counter);
|
||||
pdhResult = PdhCollectQueryData(hQuery);
|
||||
|
||||
PDH_FMT_COUNTERVALUE stFormattedValue = {0};
|
||||
pdhResult = PdhGetFormattedCounterValue(counter, PDH_FMT_LONG, NULL, &stFormattedValue);
|
||||
|
||||
if (stFormattedValue.longValue == pid)
|
||||
break;
|
||||
|
||||
instanceName += strlen(instanceName) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
TCHAR szCounterPath[1024];
|
||||
DWORD dwPathSize = 1024;
|
||||
PDH_COUNTER_PATH_ELEMENTS pe = {0};
|
||||
pe.szObjectName = "Process";
|
||||
pe.szCounterName = "% Processor Time";
|
||||
pe.szInstanceName = instanceName;
|
||||
PDH_STATUS pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &processorTimeCounter);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
|
||||
pe.szCounterName = "Elapsed Time";
|
||||
dwPathSize = 1024;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &elapsedTimeCounter);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
|
||||
pe.szCounterName = "Private Bytes";
|
||||
dwPathSize = 1024;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &privateBytesCounter);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
|
||||
pe.szCounterName = "Page Faults/sec";
|
||||
dwPathSize = 1024;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &pageFaultsPerSecondCounter);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
|
||||
pe.szCounterName = "Page File Bytes";
|
||||
dwPathSize = 1024;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &pageFileBytesCounter);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
|
||||
pe.szCounterName = "Virtual Bytes";
|
||||
dwPathSize = 1024;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &virtualBytesCounter);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
|
||||
pe.szCounterName = "Working Set - Private";
|
||||
dwPathSize = 1024;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &workingSetPrivateCounter);
|
||||
if (pdhResult != 0)
|
||||
{
|
||||
// WinXP doesn't support "Working Set - Private"
|
||||
pe.szCounterName = "Working Set";
|
||||
dwPathSize = 1024;
|
||||
pdhResult = PdhMakeCounterPath(&pe, szCounterPath, &dwPathSize, 0);
|
||||
// AYAASSERT(SUCCEEDED(pdhResult));
|
||||
pdhResult = PdhAddCounter(hQuery, szCounterPath, 0, &workingSetPrivateCounter);
|
||||
// AYAASSERT(SUCCEEDED(pdhResult));
|
||||
}
|
||||
|
||||
pdhResult = PdhAddCounter(hQuery, "\\Processor(_Total)\\% Processor Time", 0, &totalProcessorTimeCounter);
|
||||
AYAASSERT(SUCCEEDED(pdhResult));
|
||||
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
121
engine/core/src/ProcessPerfCounter.hpp
Normal file
121
engine/core/src/ProcessPerfCounter.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include "boost.hpp"
|
||||
#include "ScopedSingleton.hpp"
|
||||
|
||||
#if defined(_WIN32) && !defined(AYA_PLATFORM_DURANGO)
|
||||
#include <pdh.h>
|
||||
|
||||
class CQuery
|
||||
{
|
||||
HQUERY handle;
|
||||
|
||||
public:
|
||||
CQuery(HQUERY handle)
|
||||
: handle(handle)
|
||||
{
|
||||
}
|
||||
CQuery()
|
||||
: handle(0)
|
||||
{
|
||||
}
|
||||
HQUERY* operator&()
|
||||
{
|
||||
return &handle;
|
||||
}
|
||||
operator HQUERY() const
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
~CQuery()
|
||||
{
|
||||
PdhCloseQuery(handle);
|
||||
}
|
||||
};
|
||||
|
||||
class PerfCounter
|
||||
{
|
||||
protected:
|
||||
PerfCounter();
|
||||
CQuery hQuery;
|
||||
static void GetData2(HCOUNTER counter, long& result);
|
||||
static void GetData2(HCOUNTER counter, double& result);
|
||||
|
||||
public:
|
||||
void CollectData();
|
||||
};
|
||||
|
||||
class CProcessPerfCounter
|
||||
: public PerfCounter
|
||||
, public Aya::ScopedSingleton<CProcessPerfCounter>
|
||||
{
|
||||
public:
|
||||
CProcessPerfCounter();
|
||||
CProcessPerfCounter(int pid);
|
||||
// The number of cores used by the process
|
||||
double GetProcessCores();
|
||||
double GetElapsedTime()
|
||||
{
|
||||
double result;
|
||||
PerfCounter::GetData2(elapsedTimeCounter, result);
|
||||
return result;
|
||||
}
|
||||
long GetTotalProcessorTime()
|
||||
{
|
||||
long result;
|
||||
PerfCounter::GetData2(totalProcessorTimeCounter, result);
|
||||
return result;
|
||||
}
|
||||
long GetProcessorTime()
|
||||
{
|
||||
long result;
|
||||
PerfCounter::GetData2(processorTimeCounter, result);
|
||||
return result;
|
||||
}
|
||||
long GetPrivateBytes()
|
||||
{
|
||||
long result;
|
||||
PerfCounter::GetData2(privateBytesCounter, result);
|
||||
return result;
|
||||
}
|
||||
long GetPageFaultsPerSecond()
|
||||
{
|
||||
long result;
|
||||
PerfCounter::GetData2(pageFaultsPerSecondCounter, result);
|
||||
return result;
|
||||
}
|
||||
long GetPageFileBytes()
|
||||
{
|
||||
long result;
|
||||
PerfCounter::GetData2(pageFileBytesCounter, result);
|
||||
return result;
|
||||
}
|
||||
long GetVirtualBytes()
|
||||
{
|
||||
long result;
|
||||
PerfCounter::GetData2(virtualBytesCounter, result);
|
||||
return result;
|
||||
}
|
||||
long GetPrivateWorkingSetBytes()
|
||||
{
|
||||
long result;
|
||||
PerfCounter::GetData2(workingSetPrivateCounter, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int numCores;
|
||||
SYSTEM_INFO systemInfo;
|
||||
HCOUNTER elapsedTimeCounter;
|
||||
HCOUNTER totalProcessorTimeCounter;
|
||||
HCOUNTER processorTimeCounter;
|
||||
HCOUNTER privateBytesCounter;
|
||||
HCOUNTER pageFaultsPerSecondCounter;
|
||||
HCOUNTER pageFileBytesCounter;
|
||||
HCOUNTER virtualBytesCounter;
|
||||
HCOUNTER workingSetPrivateCounter;
|
||||
void init(int pid);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
374
engine/core/src/Profiler.cpp
Normal file
374
engine/core/src/Profiler.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
#include "Profiler.hpp"
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
#if defined(AYAPROFILER) && !defined(__APPLE__)
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4244 4995 4996)
|
||||
#endif
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
|
||||
#define MICROPROFILE_PRINTF(...) __android_log_print(ANDROID_LOG_DEBUG, "MicroProfile", __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
static void MicroProfileDebugPrintf(const char* format, ...)
|
||||
{
|
||||
char message[256];
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf_s(message, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#define MICROPROFILE_PRINTF(...) MicroProfileDebugPrintf(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define MP_ASSERT(e) AYAASSERT(e)
|
||||
#define MICROPROFILE_WEBSERVER 0
|
||||
|
||||
#ifdef AYA_PLATFORM_DURANGO
|
||||
#define MICROPROFILE_WEBSERVER_PORT 4600
|
||||
#define MICROPROFILE_CONTEXT_SWITCH_TRACE 0
|
||||
#define getenv(name) NULL
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && !defined(AYA_PLATFORM_IOS)
|
||||
#include <OpenGL/gl3.h>
|
||||
#define MICROPROFILE_GPU_TIMERS_GL 0
|
||||
#endif
|
||||
|
||||
#define MICROPROFILE_IMPL
|
||||
#include "microprofile/microprofile.hpp"
|
||||
|
||||
#define MICROPROFILE_DRAWCURSOR 1
|
||||
|
||||
#include "microprofile/microprofileui.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct InputState
|
||||
{
|
||||
int mouseX, mouseY, mouseWheel;
|
||||
bool mouseButton[4];
|
||||
};
|
||||
|
||||
Aya::Profiler::Renderer* gProfileRenderer = 0;
|
||||
|
||||
InputState gProfilerInputState;
|
||||
|
||||
void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText, uint32_t nNumCharacters)
|
||||
{
|
||||
gProfileRenderer->drawText(nX, nY, nColor, pText, nNumCharacters, MICROPROFILE_TEXT_WIDTH, MICROPROFILE_TEXT_HEIGHT);
|
||||
}
|
||||
|
||||
void MicroProfileDrawBox(int nX, int nY, int nX1, int nY1, uint32_t nColor, MicroProfileBoxType type)
|
||||
{
|
||||
if (type == MicroProfileBoxTypeBar)
|
||||
{
|
||||
uint32_t r = 0xff & (nColor >> 16);
|
||||
uint32_t g = 0xff & (nColor >> 8);
|
||||
uint32_t b = 0xff & nColor;
|
||||
|
||||
uint32_t nMax = MicroProfileMax(MicroProfileMax(MicroProfileMax(r, g), b), 30u);
|
||||
uint32_t nMin = MicroProfileMin(MicroProfileMin(MicroProfileMin(r, g), b), 180u);
|
||||
|
||||
uint32_t r0 = 0xff & ((r + nMax) / 2);
|
||||
uint32_t g0 = 0xff & ((g + nMax) / 2);
|
||||
uint32_t b0 = 0xff & ((b + nMax) / 2);
|
||||
|
||||
uint32_t r1 = 0xff & ((r + nMin) / 2);
|
||||
uint32_t g1 = 0xff & ((g + nMin) / 2);
|
||||
uint32_t b1 = 0xff & ((b + nMin) / 2);
|
||||
|
||||
uint32_t nColor0 = (r0 << 16) | (g0 << 8) | (b0 << 0) | (0xff000000 & nColor);
|
||||
uint32_t nColor1 = (r1 << 16) | (g1 << 8) | (b1 << 0) | (0xff000000 & nColor);
|
||||
|
||||
gProfileRenderer->drawBox(nX, nY, nX1, nY1, nColor0, nColor1);
|
||||
}
|
||||
else
|
||||
{
|
||||
gProfileRenderer->drawBox(nX, nY, nX1, nY1, nColor, nColor);
|
||||
}
|
||||
}
|
||||
|
||||
void MicroProfileDrawLine2D(uint32_t nVertices, float* pVertices, uint32_t nColor)
|
||||
{
|
||||
gProfileRenderer->drawLine(nVertices, const_cast<const float*>(pVertices), nColor);
|
||||
}
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Profiler
|
||||
{
|
||||
MicroProfileTokenType getTokenType(const char* group)
|
||||
{
|
||||
return strcmp(group, "GPU") == 0 ? MicroProfileTokenTypeGpu : MicroProfileTokenTypeCpu;
|
||||
}
|
||||
|
||||
Token getToken(const char* group, const char* name, int color)
|
||||
{
|
||||
return MicroProfileGetToken(group, name, color, getTokenType(group));
|
||||
}
|
||||
|
||||
Token getLabelToken(const char* group)
|
||||
{
|
||||
return MicroProfileGetLabelToken(group, getTokenType(group));
|
||||
}
|
||||
|
||||
Token getCounterToken(const char* name)
|
||||
{
|
||||
return MicroProfileGetCounterToken(name);
|
||||
}
|
||||
|
||||
uint64_t enterRegion(Token token)
|
||||
{
|
||||
return MicroProfileEnter(token);
|
||||
}
|
||||
|
||||
void addLabel(Token token, const char* name)
|
||||
{
|
||||
MicroProfileLabel(token, name);
|
||||
}
|
||||
|
||||
void addLabelFormat(Token token, const char* name, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, name);
|
||||
MicroProfileLabelFormatV(token, name, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void leaveRegion(Token token, uint64_t enterTimestamp)
|
||||
{
|
||||
MicroProfileLeave(token, enterTimestamp);
|
||||
}
|
||||
|
||||
void counterAdd(Token token, long long count)
|
||||
{
|
||||
MicroProfileCounterAdd(token, count);
|
||||
}
|
||||
|
||||
void counterSet(Token token, long long count)
|
||||
{
|
||||
MicroProfileCounterSet(token, count);
|
||||
}
|
||||
|
||||
void onThreadCreate(const char* name)
|
||||
{
|
||||
MicroProfileOnThreadCreate(name);
|
||||
}
|
||||
|
||||
void onThreadExit()
|
||||
{
|
||||
MicroProfileOnThreadExit();
|
||||
}
|
||||
|
||||
void onFrame()
|
||||
{
|
||||
if (g_MicroProfile.nWebServerDataSent)
|
||||
{
|
||||
// Set the defaults for web serving after the first connect
|
||||
MicroProfileSetEnableAllGroups(true);
|
||||
}
|
||||
|
||||
MicroProfileFlip();
|
||||
}
|
||||
|
||||
void gpuInit(void* context)
|
||||
{
|
||||
MicroProfileGpuInit(context);
|
||||
}
|
||||
|
||||
void gpuShutdown()
|
||||
{
|
||||
MicroProfileGpuShutdown();
|
||||
}
|
||||
|
||||
bool isCapturingMouseInput()
|
||||
{
|
||||
return (MicroProfileIsDrawing() && !g_MicroProfile.nRunning);
|
||||
}
|
||||
|
||||
bool handleMouse(unsigned int flags, int mouseX, int mouseY, int mouseWheel, int mouseButton)
|
||||
{
|
||||
if (flags & Flag_MouseMove)
|
||||
{
|
||||
gProfilerInputState.mouseX = mouseX;
|
||||
gProfilerInputState.mouseY = mouseY;
|
||||
}
|
||||
|
||||
if (flags & Flag_MouseWheel)
|
||||
gProfilerInputState.mouseWheel = mouseWheel;
|
||||
|
||||
if (flags & Flag_MouseDown)
|
||||
gProfilerInputState.mouseButton[mouseButton] = true;
|
||||
|
||||
if (flags & Flag_MouseUp)
|
||||
gProfilerInputState.mouseButton[mouseButton] = false;
|
||||
|
||||
MicroProfileMousePosition(std::max(gProfilerInputState.mouseX, 0), std::max(gProfilerInputState.mouseY, 0), gProfilerInputState.mouseWheel);
|
||||
MicroProfileMouseButton(gProfilerInputState.mouseButton[0], gProfilerInputState.mouseButton[1]);
|
||||
|
||||
return isCapturingMouseInput();
|
||||
}
|
||||
|
||||
bool toggleVisible()
|
||||
{
|
||||
if (MicroProfileIsDrawing())
|
||||
{
|
||||
MicroProfileSetDisplayMode(MP_DRAW_OFF);
|
||||
|
||||
if (!g_MicroProfile.nRunning)
|
||||
g_MicroProfile.nToggleRunning = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
MicroProfileSetDisplayMode(MP_DRAW_FRAME);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool forceOn()
|
||||
{
|
||||
{
|
||||
MicroProfileSetDisplayMode(MP_DRAW_FRAME);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool forceOff()
|
||||
{
|
||||
{
|
||||
MicroProfileSetDisplayMode(MP_DRAW_OFF);
|
||||
|
||||
if (!g_MicroProfile.nRunning)
|
||||
g_MicroProfile.nToggleRunning = 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool togglePause()
|
||||
{
|
||||
if (MicroProfileIsDrawing())
|
||||
{
|
||||
if (g_MicroProfile.nRunning && g_MicroProfile.nDisplay == MP_DRAW_FRAME)
|
||||
MicroProfileSetDisplayMode(MP_DRAW_DETAILED);
|
||||
else if (!g_MicroProfile.nRunning && g_MicroProfile.nDisplay == MP_DRAW_DETAILED)
|
||||
MicroProfileSetDisplayMode(MP_DRAW_FRAME);
|
||||
|
||||
MicroProfileTogglePause();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isVisible()
|
||||
{
|
||||
return MicroProfileIsDrawing();
|
||||
}
|
||||
|
||||
void render(Renderer* renderer, unsigned int width, unsigned int height)
|
||||
{
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
MicroProfileInitUI();
|
||||
|
||||
g_MicroProfileUI.nOpacityBackground = 0x40 << 24;
|
||||
}
|
||||
|
||||
gProfileRenderer = renderer;
|
||||
|
||||
MicroProfileDraw(width, height);
|
||||
|
||||
gProfileRenderer = 0;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
// MicroProfileDraw loads the preset; after it has loaded it we can check if
|
||||
// we need to set up the defaults to enable all groups
|
||||
if (g_MicroProfile.nActiveGroupWanted == 0)
|
||||
MicroProfileSetEnableAllGroups(true);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
gProfilerInputState.mouseWheel = 0;
|
||||
}
|
||||
} // namespace Profiler
|
||||
} // namespace Aya
|
||||
#else
|
||||
namespace Aya
|
||||
{
|
||||
namespace Profiler
|
||||
{
|
||||
Token getToken(const char* group, const char* name, int color)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t enterRegion(Token token)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void addLabel(Token token, const char* name) {}
|
||||
|
||||
void leaveRegion(Token token, uint64_t enterTimestamp) {}
|
||||
|
||||
void onThreadCreate(const char* name) {}
|
||||
|
||||
void onThreadExit() {}
|
||||
|
||||
void onFrame() {}
|
||||
|
||||
void gpuInit(void* context) {}
|
||||
|
||||
void gpuShutdown() {}
|
||||
|
||||
bool isCapturingMouseInput()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handleMouse(unsigned int flags, int mouseX, int mouseY, int mouseWheel, int mouseButton)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool toggleVisible()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool togglePause()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isVisible()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void render(Renderer* renderer, unsigned int width, unsigned int height) {}
|
||||
} // namespace Profiler
|
||||
} // namespace Aya
|
||||
#endif
|
||||
115
engine/core/src/Profiler.hpp
Normal file
115
engine/core/src/Profiler.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "AyaFormat.hpp" // for AYA_PRINTF_ATTR
|
||||
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
#define AYAPROFILER
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Profiler
|
||||
{
|
||||
typedef uint64_t Token;
|
||||
|
||||
Token getToken(const char* group, const char* name, int color = -1);
|
||||
Token getLabelToken(const char* group);
|
||||
Token getCounterToken(const char* name);
|
||||
|
||||
uint64_t enterRegion(Token token);
|
||||
void leaveRegion(Token token, uint64_t enterTimestamp);
|
||||
|
||||
void addLabel(Token token, const char* name);
|
||||
AYA_PRINTF_ATTR(2, 3) void addLabelFormat(Token token, const char* name, ...);
|
||||
|
||||
void counterAdd(Token token, long long count);
|
||||
void counterSet(Token token, long long count);
|
||||
|
||||
void onThreadCreate(const char* name);
|
||||
void onThreadExit();
|
||||
|
||||
void onFrame();
|
||||
|
||||
enum Flags
|
||||
{
|
||||
Flag_MouseMove = 1 << 0,
|
||||
Flag_MouseWheel = 1 << 1,
|
||||
Flag_MouseDown = 1 << 2,
|
||||
Flag_MouseUp = 1 << 3,
|
||||
};
|
||||
|
||||
void gpuInit(void* context);
|
||||
void gpuShutdown();
|
||||
|
||||
bool isCapturingMouseInput();
|
||||
bool handleMouse(unsigned int flags, int mouseX, int mouseY, int mouseWheel, int mouseButton);
|
||||
|
||||
bool forceOn();
|
||||
bool forceOff();
|
||||
bool toggleVisible();
|
||||
bool togglePause();
|
||||
|
||||
struct Renderer
|
||||
{
|
||||
virtual ~Renderer() {}
|
||||
|
||||
virtual void drawText(
|
||||
int x, int y, unsigned int color, const char* text, unsigned int length, unsigned int textWidth, unsigned int textHeight) = 0;
|
||||
virtual void drawBox(int x0, int y0, int x1, int y1, unsigned int color0, unsigned int color1) = 0;
|
||||
virtual void drawLine(unsigned int vertexCount, const float* vertexData, unsigned int color) = 0;
|
||||
};
|
||||
|
||||
bool isVisible();
|
||||
void render(Renderer* renderer, unsigned int width, unsigned int height);
|
||||
|
||||
struct Scope
|
||||
{
|
||||
Token token;
|
||||
uint64_t timestamp;
|
||||
|
||||
Scope(Token token)
|
||||
: token(token)
|
||||
{
|
||||
timestamp = enterRegion(token);
|
||||
}
|
||||
|
||||
~Scope()
|
||||
{
|
||||
leaveRegion(token, timestamp);
|
||||
}
|
||||
};
|
||||
} // namespace Profiler
|
||||
} // namespace Aya
|
||||
|
||||
#define AYAPROFILER_TOKEN_PASTE0(a, b) a##b
|
||||
#define AYAPROFILER_TOKEN_PASTE(a, b) AYAPROFILER_TOKEN_PASTE0(a, b)
|
||||
|
||||
#ifdef AYAPROFILER
|
||||
#define AYAPROFILER_SCOPE(group, name, ...) \
|
||||
static ::Aya::Profiler::Token AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__) = ::Aya::Profiler::getToken(group "", name "", ##__VA_ARGS__); \
|
||||
::Aya::Profiler::Scope AYAPROFILER_TOKEN_PASTE(profscope, __LINE__)(AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__))
|
||||
#define AYAPROFILER_LABEL(group, label) \
|
||||
static ::Aya::Profiler::Token AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__) = ::Aya::Profiler::getLabelToken(group ""); \
|
||||
::Aya::Profiler::addLabel(AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__), label)
|
||||
#define AYAPROFILER_LABELF(group, label, ...) \
|
||||
static ::Aya::Profiler::Token AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__) = ::Aya::Profiler::getLabelToken(group ""); \
|
||||
::Aya::Profiler::addLabelFormat(AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__), label, ##__VA_ARGS__)
|
||||
#define AYAPROFILER_COUNTER_ADD(name, count) \
|
||||
static ::Aya::Profiler::Token AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__) = ::Aya::Profiler::getCounterToken(name ""); \
|
||||
::Aya::Profiler::counterAdd(AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__), static_cast<long long>(count))
|
||||
#define AYAPROFILER_COUNTER_SUB(name, count) \
|
||||
static ::Aya::Profiler::Token AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__) = ::Aya::Profiler::getCounterToken(name ""); \
|
||||
::Aya::Profiler::counterAdd(AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__), -static_cast<long long>(count))
|
||||
#define AYAPROFILER_COUNTER_SET(name, count) \
|
||||
static ::Aya::Profiler::Token AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__) = ::Aya::Profiler::getCounterToken(name ""); \
|
||||
::Aya::Profiler::counterSet(AYAPROFILER_TOKEN_PASTE(proftoken, __LINE__), count)
|
||||
#else
|
||||
#define AYAPROFILER_SCOPE(group, name, ...) (void)0
|
||||
#define AYAPROFILER_LABEL(group, label) (void)0
|
||||
#define AYAPROFILER_LABELF(group, label, ...) (void)sizeof(0, __VA_ARGS__)
|
||||
#define AYAPROFILER_COUNTER_ADD(name, count) (void)0
|
||||
#define AYAPROFILER_COUNTER_SUB(name, count) (void)0
|
||||
#define AYAPROFILER_COUNTER_SET(name, count) (void)0
|
||||
#endif
|
||||
400
engine/core/src/RegistryUtil.cpp
Normal file
400
engine/core/src/RegistryUtil.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
@file RegistryUtil.cpp
|
||||
|
||||
@created 2006-04-06
|
||||
@edited 2006-04-24
|
||||
|
||||
Copyright 2000-2006, Morgan McGuire.
|
||||
All rights reserved.
|
||||
*/
|
||||
// This prevent inclusion of winsock.h in Windows.h, which prevents windows redifinition errors
|
||||
// Look at winsock2.h for details, winsock2.h is #included from boost.hpp & other places.
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCKAPI_
|
||||
|
||||
|
||||
#include "RegistryUtil.hpp"
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
#include "Debug.hpp"
|
||||
|
||||
|
||||
// declare HKEY constants as needed for VC6
|
||||
#if !defined(HKEY_PERFORMANCE_DATA)
|
||||
#define HKEY_PERFORMANCE_DATA ((HKEY)((LONG)0x80000004))
|
||||
#endif
|
||||
#if !defined(HKEY_PERFORMANCE_TEXT)
|
||||
#define HKEY_PERFORMANCE_TEXT ((HKEY)((LONG)0x80000050))
|
||||
#endif
|
||||
#if !defined(HKEY_PERFORMANCE_NLSTEXT)
|
||||
#define HKEY_PERFORMANCE_NLSTEXT ((HKEY)((LONG)0x80000060))
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
// static helpers
|
||||
static HKEY getKeyFromString(const char* str, UINT32 length);
|
||||
|
||||
|
||||
bool RegistryUtil::keyExists(const std::string& key)
|
||||
{
|
||||
size_t pos = key.find('\\', 0);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hkey = getKeyFromString(key.c_str(), pos);
|
||||
|
||||
if (hkey == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AYAASSERT(key.size() > (pos + 1));
|
||||
HKEY openKey;
|
||||
INT32 result = RegOpenKeyEx(hkey, (key.c_str() + pos + 1), 0, KEY_READ, &openKey);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
RegCloseKey(openKey);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RegistryUtil::read32bitNumber(const std::string& key, INT32& valueData)
|
||||
{
|
||||
|
||||
size_t pos = key.find('\\', 0);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hkey = getKeyFromString(key.c_str(), pos);
|
||||
|
||||
if (hkey == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AYAASSERT(key.size() > (pos + 1));
|
||||
|
||||
size_t valuePos = key.rfind('\\');
|
||||
|
||||
if (valuePos != std::string::npos)
|
||||
{
|
||||
std::string subKey = key.substr(pos + 1, valuePos - pos - 1);
|
||||
std::string value = key.substr(valuePos + 1, key.size() - valuePos);
|
||||
|
||||
HKEY openKey;
|
||||
INT32 result = RegOpenKeyEx(hkey, subKey.c_str(), 0, KEY_READ, &openKey);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
UINT32 dataSize = sizeof(INT32);
|
||||
result = RegQueryValueEx(openKey, value.c_str(), NULL, NULL, reinterpret_cast<LPBYTE>(&valueData), reinterpret_cast<LPDWORD>(&dataSize));
|
||||
|
||||
AYAASSERT(result == ERROR_SUCCESS && "Could not read registry key value.");
|
||||
|
||||
RegCloseKey(openKey);
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegistryUtil::readBinaryData(const std::string& key, BYTE* valueData, UINT32& dataSize)
|
||||
{
|
||||
size_t pos = key.find('\\', 0);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hkey = getKeyFromString(key.c_str(), pos);
|
||||
|
||||
if (hkey == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AYAASSERT(key.size() > (pos + 1));
|
||||
|
||||
size_t valuePos = key.rfind('\\');
|
||||
|
||||
if (valuePos != std::string::npos)
|
||||
{
|
||||
std::string subKey = key.substr(pos + 1, valuePos - pos - 1);
|
||||
std::string value = key.substr(valuePos + 1, key.size() - valuePos);
|
||||
|
||||
HKEY openKey;
|
||||
INT32 result = RegOpenKeyEx(hkey, subKey.c_str(), 0, KEY_READ, &openKey);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
|
||||
if (valueData == NULL)
|
||||
{
|
||||
result = RegQueryValueEx(openKey, value.c_str(), NULL, NULL, NULL, reinterpret_cast<LPDWORD>(&dataSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
result = RegQueryValueEx(openKey, value.c_str(), NULL, NULL, valueData, reinterpret_cast<LPDWORD>(&dataSize));
|
||||
}
|
||||
|
||||
AYAASSERT(result == ERROR_SUCCESS && "Could not read registry key value.");
|
||||
|
||||
RegCloseKey(openKey);
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegistryUtil::readString(const std::string& key, std::string& valueData)
|
||||
{
|
||||
size_t pos = key.find('\\', 0);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hkey = getKeyFromString(key.c_str(), pos);
|
||||
|
||||
if (hkey == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AYAASSERT(key.size() > (pos + 1));
|
||||
|
||||
size_t valuePos = key.rfind('\\');
|
||||
|
||||
if (valuePos != std::string::npos)
|
||||
{
|
||||
std::string subKey = key.substr(pos + 1, valuePos - pos - 1);
|
||||
std::string value = key.substr(valuePos + 1, key.size() - valuePos);
|
||||
|
||||
HKEY openKey;
|
||||
INT32 result = RegOpenKeyEx(hkey, subKey.c_str(), 0, KEY_READ, &openKey);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
UINT32 dataSize = 0;
|
||||
|
||||
result = RegQueryValueEx(openKey, value.c_str(), NULL, NULL, NULL, reinterpret_cast<LPDWORD>(&dataSize));
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
char* tmpStr = new char[dataSize];
|
||||
memset(tmpStr, 0, dataSize);
|
||||
|
||||
result = RegQueryValueEx(openKey, value.c_str(), NULL, NULL, reinterpret_cast<LPBYTE>(tmpStr), reinterpret_cast<LPDWORD>(&dataSize));
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
valueData = tmpStr;
|
||||
}
|
||||
|
||||
delete[] tmpStr;
|
||||
}
|
||||
// AYAASSERT(result == ERROR_SUCCESS && "Could not read registry key value.");
|
||||
|
||||
RegCloseKey(openKey);
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegistryUtil::write32bitNumber(const std::string& key, INT32 valueData)
|
||||
{
|
||||
|
||||
size_t pos = key.find('\\', 0);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hkey = getKeyFromString(key.c_str(), pos);
|
||||
|
||||
if (hkey == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AYAASSERT(key.size() > (pos + 1));
|
||||
|
||||
size_t valuePos = key.rfind('\\');
|
||||
|
||||
if (valuePos != std::string::npos)
|
||||
{
|
||||
std::string subKey = key.substr(pos + 1, valuePos - pos - 1);
|
||||
std::string value = key.substr(valuePos + 1, key.size() - valuePos);
|
||||
|
||||
HKEY openKey;
|
||||
INT32 result = RegOpenKeyEx(hkey, subKey.c_str(), 0, KEY_ALL_ACCESS, &openKey);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
result = RegSetValueEx(openKey, value.c_str(), NULL, REG_DWORD, reinterpret_cast<const BYTE*>(&valueData), sizeof(INT32));
|
||||
|
||||
AYAASSERT(result == ERROR_SUCCESS && "Could not write registry key value.");
|
||||
|
||||
RegCloseKey(openKey);
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegistryUtil::writeBinaryData(const std::string& key, const BYTE* valueData, UINT32 dataSize)
|
||||
{
|
||||
AYAASSERT(valueData);
|
||||
|
||||
size_t pos = key.find('\\', 0);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hkey = getKeyFromString(key.c_str(), pos);
|
||||
|
||||
if (hkey == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AYAASSERT(key.size() > (pos + 1));
|
||||
|
||||
size_t valuePos = key.rfind('\\');
|
||||
|
||||
if (valuePos != std::string::npos)
|
||||
{
|
||||
std::string subKey = key.substr(pos + 1, valuePos - pos - 1);
|
||||
std::string value = key.substr(valuePos + 1, key.size() - valuePos);
|
||||
|
||||
HKEY openKey;
|
||||
INT32 result = RegOpenKeyEx(hkey, subKey.c_str(), 0, KEY_ALL_ACCESS, &openKey);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
|
||||
if (valueData)
|
||||
{
|
||||
result = RegSetValueEx(openKey, value.c_str(), NULL, REG_BINARY, reinterpret_cast<const BYTE*>(valueData), dataSize);
|
||||
}
|
||||
|
||||
AYAASSERT(result == ERROR_SUCCESS && "Could not write registry key value.");
|
||||
|
||||
RegCloseKey(openKey);
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegistryUtil::writeString(const std::string& key, const std::string& valueData)
|
||||
{
|
||||
size_t pos = key.find('\\', 0);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hkey = getKeyFromString(key.c_str(), pos);
|
||||
|
||||
if (hkey == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AYAASSERT(key.size() > (pos + 1));
|
||||
|
||||
size_t valuePos = key.rfind('\\');
|
||||
|
||||
if (valuePos != std::string::npos)
|
||||
{
|
||||
std::string subKey = key.substr(pos + 1, valuePos - pos - 1);
|
||||
std::string value = key.substr(valuePos + 1, key.size() - valuePos);
|
||||
|
||||
HKEY openKey;
|
||||
INT32 result = RegOpenKeyEx(hkey, subKey.c_str(), 0, KEY_ALL_ACCESS, &openKey);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
result = RegSetValueEx(openKey, value.c_str(), NULL, REG_SZ, reinterpret_cast<const BYTE*>(valueData.c_str()), (valueData.size() + 1));
|
||||
AYAASSERT(result == ERROR_SUCCESS && "Could not write registry key value.");
|
||||
|
||||
RegCloseKey(openKey);
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// static helpers
|
||||
static HKEY getKeyFromString(const char* str, UINT32 length)
|
||||
{
|
||||
AYAASSERT(str);
|
||||
|
||||
if (str)
|
||||
{
|
||||
if (strncmp(str, "HKEY_CLASSES_ROOT", length) == 0)
|
||||
{
|
||||
return HKEY_CLASSES_ROOT;
|
||||
}
|
||||
else if (strncmp(str, "HKEY_CURRENT_CONFIG", length) == 0)
|
||||
{
|
||||
return HKEY_CURRENT_CONFIG;
|
||||
}
|
||||
else if (strncmp(str, "HKEY_CURRENT_USER", length) == 0)
|
||||
{
|
||||
return HKEY_CURRENT_USER;
|
||||
}
|
||||
else if (strncmp(str, "HKEY_LOCAL_MACHINE", length) == 0)
|
||||
{
|
||||
return HKEY_LOCAL_MACHINE;
|
||||
}
|
||||
else if (strncmp(str, "HKEY_PERFORMANCE_DATA", length) == 0)
|
||||
{
|
||||
return HKEY_PERFORMANCE_DATA;
|
||||
}
|
||||
else if (strncmp(str, "HKEY_PERFORMANCE_NLSTEXT", length) == 0)
|
||||
{
|
||||
return HKEY_PERFORMANCE_NLSTEXT;
|
||||
}
|
||||
else if (strncmp(str, "HKEY_PERFORMANCE_TEXT", length) == 0)
|
||||
{
|
||||
return HKEY_PERFORMANCE_TEXT;
|
||||
}
|
||||
else if (strncmp(str, "HKEY_CLASSES_ROOT", length) == 0)
|
||||
{
|
||||
return HKEY_CLASSES_ROOT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
#endif
|
||||
71
engine/core/src/RegistryUtil.hpp
Normal file
71
engine/core/src/RegistryUtil.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32) && !defined(AYA_PLATFORM_DURANGO)
|
||||
#include "pdh.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
/**
|
||||
Provides generalized Windows registry querying.
|
||||
|
||||
All key names are one string in the format:
|
||||
"[base key]\[sub-keys]\value"
|
||||
|
||||
[base key] can be any of the following:
|
||||
HKEY_CLASSES_ROOT
|
||||
HKEY_CURRENT_CONFIG
|
||||
HKEY_CURRENT_USER
|
||||
HKEY_LOCAL_MACHINE
|
||||
HKEY_PERFORMANCE_DATA
|
||||
HKEY_PERFORMANCE_NLSTEXT
|
||||
HKEY_PERFORMANCE_TEXT
|
||||
HKEY_USERS
|
||||
|
||||
keyExists() should be used to validate a key before reading or writing
|
||||
to ensure that a debug assert or false return is for a different error.
|
||||
*/
|
||||
class RegistryUtil
|
||||
{
|
||||
|
||||
public:
|
||||
/** returns true if the key exists */
|
||||
static bool keyExists(const std::string& key);
|
||||
|
||||
/** returns false if the key could not be read for any reason. */
|
||||
static bool read32bitNumber(const std::string& key, INT32& valueData);
|
||||
|
||||
/**
|
||||
Reads an arbitrary amount of data from a binary registry key.
|
||||
returns false if the key could not be read for any reason.
|
||||
|
||||
@param valueData pointer to the output buffer of sufficient size. Pass NULL as valueData in order to have available data size returned in
|
||||
dataSize.
|
||||
@param dataSize size of the output buffer. When NULL is passed for valueData, contains the size of available data on successful return.
|
||||
*/
|
||||
static bool readBinaryData(const std::string& key, BYTE* valueData, UINT32& dataSize);
|
||||
|
||||
/** returns false if the key could not be read for any reason. */
|
||||
static bool readString(const std::string& key, std::string& valueData);
|
||||
|
||||
/** returns false if the key could not be written for any reason. */
|
||||
static bool write32bitNumber(const std::string& key, INT32 valueData);
|
||||
|
||||
/**
|
||||
Writes an arbitrary amount of data to a binary registry key.
|
||||
returns false if the key could not be written for any reason.
|
||||
|
||||
@param valueData pointer to the input buffer
|
||||
@param dataSize size of the input buffer that should be written
|
||||
*/
|
||||
static bool writeBinaryData(const std::string& key, const BYTE* valueData, UINT32 dataSize);
|
||||
|
||||
/** returns false if the key could not be written for any reason. */
|
||||
static bool writeString(const std::string& key, const std::string& valueData);
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
#endif // _WIN32
|
||||
931
engine/core/src/RunningAverage.hpp
Normal file
931
engine/core/src/RunningAverage.hpp
Normal file
@@ -0,0 +1,931 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "atomic.hpp"
|
||||
#include "boost.hpp"
|
||||
#include "time.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include "MathUtil.hpp"
|
||||
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/array.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
template<typename ValueType = double, typename AverageType = double>
|
||||
class RunningAverage
|
||||
{
|
||||
boost::shared_ptr<boost::circular_buffer<ValueType>> buffer;
|
||||
|
||||
public:
|
||||
RunningAverage(double lerp = 0.05, ValueType initialValue = 0, unsigned int bufferSize = 0)
|
||||
: lerp(lerp)
|
||||
, lastSampleValue(initialValue)
|
||||
, averageValue(initialValue)
|
||||
, averageVariance(0)
|
||||
, firstTime(true)
|
||||
{
|
||||
if (bufferSize)
|
||||
buffer.reset(new boost::circular_buffer<ValueType>(bufferSize));
|
||||
}
|
||||
|
||||
void sample(ValueType value)
|
||||
{
|
||||
if (isFinite(value))
|
||||
{
|
||||
sampleValue(value);
|
||||
sampleVariance(value);
|
||||
|
||||
if (buffer)
|
||||
buffer->push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current value
|
||||
AverageType value() const
|
||||
{
|
||||
return averageValue;
|
||||
}
|
||||
|
||||
AverageType variance() const
|
||||
{
|
||||
return averageVariance;
|
||||
}
|
||||
AverageType standard_deviation() const
|
||||
{
|
||||
// TODO: Cache this value?
|
||||
return std::sqrt(averageVariance);
|
||||
}
|
||||
AverageType variance_to_mean_ratio() const
|
||||
{
|
||||
return averageVariance / (averageValue * averageValue);
|
||||
}
|
||||
AverageType coefficient_of_variation() const
|
||||
{
|
||||
return standard_deviation() / averageValue;
|
||||
}
|
||||
|
||||
// Get the last sampled value
|
||||
ValueType lastSample() const
|
||||
{
|
||||
return lastSampleValue;
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void iter(F& f) const
|
||||
{
|
||||
if (buffer)
|
||||
{
|
||||
for (typename boost::circular_buffer<ValueType>::const_iterator it = buffer->begin(); it != buffer->end(); ++it)
|
||||
{
|
||||
f(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset(ValueType resetValue = 0)
|
||||
{
|
||||
lastSampleValue = resetValue;
|
||||
averageValue = resetValue;
|
||||
averageVariance = 0;
|
||||
firstTime = true;
|
||||
|
||||
if (buffer)
|
||||
buffer->clear();
|
||||
}
|
||||
|
||||
bool hasSampled()
|
||||
{
|
||||
return !firstTime;
|
||||
}
|
||||
|
||||
const double lerp;
|
||||
|
||||
private:
|
||||
ValueType lastSampleValue;
|
||||
AverageType averageValue;
|
||||
AverageType averageVariance;
|
||||
bool firstTime;
|
||||
|
||||
inline void sampleValue(ValueType value)
|
||||
{
|
||||
averageValue = firstTime ? value : (1.0 - lerp) * averageValue + lerp * (AverageType)value;
|
||||
lastSampleValue = value;
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
inline void sampleVariance(ValueType value)
|
||||
{
|
||||
const double diff = value - averageValue;
|
||||
const double variance = diff * diff;
|
||||
averageVariance = (1.0 - lerp) * averageVariance + lerp * variance;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename ValueType = double, typename AverageType = double>
|
||||
class WindowAverage
|
||||
{
|
||||
protected:
|
||||
boost::circular_buffer<ValueType> buffer;
|
||||
|
||||
public:
|
||||
struct Stats
|
||||
{
|
||||
Stats(size_t samples, const AverageType& average, const AverageType& variance)
|
||||
: samples(samples)
|
||||
, average(average)
|
||||
, variance(variance) {};
|
||||
size_t samples;
|
||||
AverageType average;
|
||||
AverageType variance;
|
||||
};
|
||||
|
||||
WindowAverage(size_t maxSamples)
|
||||
: buffer(maxSamples)
|
||||
{
|
||||
}
|
||||
void setMaxSamples(size_t maxSamples)
|
||||
{
|
||||
buffer.set_capacity(maxSamples);
|
||||
}
|
||||
size_t getMaxSamples() const
|
||||
{
|
||||
return buffer.capacity();
|
||||
}
|
||||
|
||||
void sample(ValueType value)
|
||||
{
|
||||
buffer.push_back(value);
|
||||
}
|
||||
|
||||
// calls fonbeforedrop if an item is about to be dropped
|
||||
template<class F>
|
||||
void sample(ValueType value, F& fonbeforedrop)
|
||||
{
|
||||
if (buffer.full())
|
||||
{
|
||||
fonbeforedrop(buffer.front());
|
||||
}
|
||||
|
||||
sample(value);
|
||||
}
|
||||
|
||||
Stats getSanitizedStats(Confidence conf = C90) const
|
||||
{
|
||||
Stats regularStats = getStats();
|
||||
if (regularStats.samples <= 1)
|
||||
return regularStats;
|
||||
|
||||
Stats result(0, AverageType(), AverageType());
|
||||
|
||||
AverageType std = sqrt(regularStats.variance);
|
||||
|
||||
for (typename boost::circular_buffer<ValueType>::const_iterator it = buffer.begin(); it != buffer.end(); ++it)
|
||||
{
|
||||
double value = *it;
|
||||
|
||||
if (IsValueOutlier(value, regularStats.samples, regularStats.average, std, conf))
|
||||
continue;
|
||||
|
||||
result.samples++;
|
||||
result.average += value;
|
||||
}
|
||||
|
||||
result.average /= result.samples;
|
||||
|
||||
for (typename boost::circular_buffer<ValueType>::const_iterator it = buffer.begin(); it != buffer.end(); ++it)
|
||||
{
|
||||
double value = *it;
|
||||
|
||||
if (IsValueOutlier(value, regularStats.samples, regularStats.average, std, conf))
|
||||
continue;
|
||||
|
||||
AverageType diff = (result.average - value);
|
||||
result.variance = result.variance + diff * diff;
|
||||
}
|
||||
|
||||
result.variance /= (result.samples - 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Stats getStats(size_t samples = ~0) const // get n last frames.
|
||||
{
|
||||
samples = std::min(buffer.size(), samples);
|
||||
Stats result(samples, AverageType(), AverageType());
|
||||
|
||||
typename boost::circular_buffer<ValueType>::const_reverse_iterator it;
|
||||
size_t ii;
|
||||
for (it = buffer.rbegin(), ii = 0; ii < samples; ++it, ++ii)
|
||||
{
|
||||
result.average = result.average + *it;
|
||||
}
|
||||
|
||||
if (samples != 0)
|
||||
{
|
||||
result.average /= samples;
|
||||
}
|
||||
|
||||
for (it = buffer.rbegin(), ii = 0; ii < samples; ++it, ++ii)
|
||||
{
|
||||
AverageType diff = (result.average - *it);
|
||||
result.variance = result.variance + diff * diff;
|
||||
}
|
||||
if (samples > 1)
|
||||
{
|
||||
result.variance /= (samples - 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AverageType getLatest() const // get data from the last frame
|
||||
{
|
||||
return buffer.empty() ? 0 : *(buffer.rbegin());
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void iter(F& f) const
|
||||
{
|
||||
for (typename boost::circular_buffer<ValueType>::const_iterator it = buffer.begin(); it != buffer.end(); ++it)
|
||||
{
|
||||
f(*it);
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return buffer.size();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A class that follows the pattern of RunningAverage, but keeps track of step frequency.
|
||||
template<Time::SampleMethod sampleMethod = Time::Benchmark>
|
||||
class RunningAverageTimeInterval
|
||||
{
|
||||
public:
|
||||
RunningAverageTimeInterval(double lerp = 0.05)
|
||||
: firstTime(true)
|
||||
, average(lerp)
|
||||
{
|
||||
}
|
||||
|
||||
void sample()
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
timer.reset();
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
average.sample(timer.reset().seconds());
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current value
|
||||
Time::Interval value() const
|
||||
{
|
||||
Time::Interval timeSinceLastSample = timer.delta();
|
||||
double v = average.value();
|
||||
if (timeSinceLastSample.seconds() > 2.0 * v)
|
||||
return timeSinceLastSample;
|
||||
else
|
||||
return Time::Interval(v);
|
||||
}
|
||||
|
||||
double rate() const
|
||||
{
|
||||
double v = value().seconds();
|
||||
return v > 0.0 ? 1.0 / v : 0.0;
|
||||
}
|
||||
|
||||
double variance() const
|
||||
{
|
||||
return average.variance();
|
||||
}
|
||||
double standard_deviation() const
|
||||
{
|
||||
return average.standard_deviation();
|
||||
}
|
||||
double variance_to_mean_ratio() const
|
||||
{
|
||||
return average.variance_to_mean_ratio();
|
||||
}
|
||||
double coefficient_of_variation() const
|
||||
{
|
||||
return average.coefficient_of_variation();
|
||||
}
|
||||
double getLerp() const
|
||||
{
|
||||
return average.lerp;
|
||||
}
|
||||
|
||||
Time::Interval lastSample() const
|
||||
{
|
||||
return Time::Interval(average.lastSample());
|
||||
}
|
||||
|
||||
private:
|
||||
Timer<sampleMethod> timer;
|
||||
bool firstTime;
|
||||
RunningAverage<> average;
|
||||
};
|
||||
|
||||
struct FOnBeforeDrop
|
||||
{
|
||||
WindowAverage<>& average;
|
||||
Time::Interval& currentWindow;
|
||||
Time::Interval& maxWindow;
|
||||
FOnBeforeDrop(WindowAverage<>& average, Time::Interval& currentWindow, Time::Interval& maxWindow)
|
||||
: average(average)
|
||||
, currentWindow(currentWindow)
|
||||
, maxWindow(maxWindow) {};
|
||||
|
||||
void operator()(double sample)
|
||||
{
|
||||
if (currentWindow.seconds() < maxWindow.seconds())
|
||||
{
|
||||
// prevent dropping.
|
||||
// grow window size.
|
||||
average.setMaxSamples(average.getMaxSamples() * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// allow drop. adjust total counter.
|
||||
currentWindow -= Time::Interval(sample);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// A class that follows the pattern of RunningAverage, but keeps track of step frequency.
|
||||
template<Time::SampleMethod sampleMethod = Time::Benchmark>
|
||||
class WindowAverageTimeInterval
|
||||
{
|
||||
public:
|
||||
WindowAverageTimeInterval(Time::Interval maxWindow)
|
||||
: maxWindow(maxWindow)
|
||||
, average(16)
|
||||
{
|
||||
}
|
||||
|
||||
void setMaxWindow(Time::Interval maxWindow)
|
||||
{
|
||||
this->maxWindow = maxWindow;
|
||||
if (maxWindow.seconds() == 0.0)
|
||||
{
|
||||
// special case, release memory
|
||||
average.setMaxSamples(16);
|
||||
}
|
||||
};
|
||||
Time::Interval getMaxWindow() const
|
||||
{
|
||||
return maxWindow;
|
||||
};
|
||||
size_t getCapacity() const
|
||||
{
|
||||
return average.getMaxSamples();
|
||||
};
|
||||
|
||||
void sample()
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
timer.reset();
|
||||
firstTime = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
double dt = timer.reset().seconds();
|
||||
currentWindow += Time::Interval(dt);
|
||||
|
||||
// this functor will allow our ring buffer to grow geometrically
|
||||
// as long as it doesn't contain maxWindow worth of interval measurments.
|
||||
FOnBeforeDrop fonbeforedrop(average, currentWindow, maxWindow);
|
||||
average.sample(dt, fonbeforedrop);
|
||||
}
|
||||
}
|
||||
|
||||
struct Stats
|
||||
{
|
||||
Stats(size_t samples, Time::Interval averagedt, Time::Interval variancedt, Time::Interval totalt, double samplespersecond)
|
||||
: samples(samples)
|
||||
, average(averagedt)
|
||||
, variance(variancedt)
|
||||
, sum(totalt)
|
||||
, samplespersecond(samplespersecond) {};
|
||||
size_t samples;
|
||||
Time::Interval average;
|
||||
Time::Interval variance;
|
||||
Time::Interval sum;
|
||||
double samplespersecond;
|
||||
};
|
||||
|
||||
struct FSum
|
||||
{
|
||||
FSum()
|
||||
: vsum(0.0) {};
|
||||
|
||||
double vsum;
|
||||
void operator()(double v)
|
||||
{
|
||||
vsum += v;
|
||||
};
|
||||
};
|
||||
|
||||
Stats getStats(size_t samples = ~0) const
|
||||
{
|
||||
WindowAverage<>::Stats basicstats = average.getStats(samples);
|
||||
|
||||
FSum fsum;
|
||||
|
||||
average.iter(fsum);
|
||||
|
||||
return Stats(basicstats.samples, Time::Interval(basicstats.average), Time::Interval(basicstats.variance), Time::Interval(fsum.vsum),
|
||||
samples / fsum.vsum);
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void iter(F& f) const
|
||||
{
|
||||
average.iter(f);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
firstTime = true;
|
||||
average.clear();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return average.size();
|
||||
}
|
||||
|
||||
private:
|
||||
Time::Interval currentWindow;
|
||||
Time::Interval maxWindow;
|
||||
WindowAverage<> average;
|
||||
Timer<sampleMethod> timer;
|
||||
bool firstTime;
|
||||
};
|
||||
|
||||
template<typename ValueType = int, Time::SampleMethod sampleMethod = Time::Benchmark>
|
||||
class TotalCountTimeInterval
|
||||
{
|
||||
Timer<sampleMethod> timer;
|
||||
double interval;
|
||||
ValueType valueLastInterval;
|
||||
ValueType valueCurrentInterval;
|
||||
|
||||
public:
|
||||
TotalCountTimeInterval(double interval = 1.0f)
|
||||
: interval(interval)
|
||||
, valueCurrentInterval(0)
|
||||
, valueLastInterval(0)
|
||||
{
|
||||
}
|
||||
void increment(ValueType count = 1)
|
||||
{
|
||||
if (timer.delta().seconds() >= interval)
|
||||
{
|
||||
valueLastInterval = valueCurrentInterval;
|
||||
valueCurrentInterval = 0;
|
||||
timer.reset();
|
||||
}
|
||||
valueCurrentInterval += count;
|
||||
}
|
||||
void decrement(ValueType count = 1)
|
||||
{
|
||||
if (timer.delta().seconds() >= interval)
|
||||
{
|
||||
valueLastInterval = valueCurrentInterval;
|
||||
valueCurrentInterval = 0;
|
||||
timer.reset();
|
||||
}
|
||||
valueCurrentInterval -= count;
|
||||
}
|
||||
|
||||
ValueType getCount() const
|
||||
{
|
||||
return (timer.delta().seconds() <= interval) ? valueLastInterval : 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ThrottlingHelper
|
||||
{
|
||||
int* eventsPerMinute;
|
||||
int* eventsPerObjectPerMinute;
|
||||
int requestCounter;
|
||||
int maxObjectCount;
|
||||
Time lastTimestamp;
|
||||
|
||||
public:
|
||||
ThrottlingHelper(int* eventsPerMinute, int* eventsPerObjectPerMinute = NULL)
|
||||
: // Designed to pass FInt
|
||||
eventsPerMinute(eventsPerMinute)
|
||||
, eventsPerObjectPerMinute(eventsPerObjectPerMinute)
|
||||
, requestCounter(0)
|
||||
, maxObjectCount(0)
|
||||
, lastTimestamp(Time::nowFast())
|
||||
{
|
||||
}
|
||||
|
||||
bool checkLimit(int objectCount = 0)
|
||||
{
|
||||
Time now = Time::nowFast();
|
||||
|
||||
if ((now - lastTimestamp).seconds() > 60)
|
||||
{
|
||||
requestCounter = 0;
|
||||
lastTimestamp = now;
|
||||
maxObjectCount = 0;
|
||||
}
|
||||
|
||||
requestCounter++;
|
||||
maxObjectCount = std::max(objectCount, maxObjectCount);
|
||||
|
||||
int totalCount = *eventsPerMinute;
|
||||
if (eventsPerObjectPerMinute)
|
||||
totalCount += maxObjectCount * (*eventsPerObjectPerMinute);
|
||||
|
||||
if (requestCounter > totalCount)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class BudgetedThrottlingHelper
|
||||
{
|
||||
float currentBudget;
|
||||
|
||||
public:
|
||||
BudgetedThrottlingHelper()
|
||||
: currentBudget(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
void addBudget(float budget, float maxBudget)
|
||||
{
|
||||
currentBudget = std::min(currentBudget + budget, maxBudget);
|
||||
}
|
||||
|
||||
bool checkAndReduceBudget()
|
||||
{
|
||||
if (currentBudget < 0)
|
||||
return false;
|
||||
|
||||
currentBudget--;
|
||||
return true;
|
||||
}
|
||||
|
||||
float getBudget()
|
||||
{
|
||||
return currentBudget;
|
||||
};
|
||||
};
|
||||
|
||||
// A class that follows the pattern of RunningAverage, but keeps track of time spent in a cyclical task.
|
||||
template<Time::SampleMethod sampleMethod = Time::Benchmark>
|
||||
class RunningAverageDutyCycle
|
||||
{
|
||||
public:
|
||||
RunningAverageDutyCycle(double lerp)
|
||||
: time(lerp)
|
||||
, interval(lerp)
|
||||
{
|
||||
}
|
||||
|
||||
RunningAverageDutyCycle(double lerp, int timeBufferSize)
|
||||
: time(lerp, 0, timeBufferSize)
|
||||
, interval(lerp)
|
||||
{
|
||||
}
|
||||
|
||||
void sample(Time::Interval elapsedTime)
|
||||
{
|
||||
interval.sample();
|
||||
time.sample(elapsedTime.seconds());
|
||||
}
|
||||
|
||||
Aya::Time startSample() const
|
||||
{
|
||||
return Time::now<sampleMethod>();
|
||||
}
|
||||
|
||||
void stopSample(Aya::Time start)
|
||||
{
|
||||
sample(Time::now<sampleMethod>() - start);
|
||||
}
|
||||
|
||||
double dutyCycle() const
|
||||
{
|
||||
double averageInterval = interval.value().seconds();
|
||||
double averageTime = time.value();
|
||||
return averageInterval != 0 ? averageTime / averageInterval : (averageTime > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
double rate() const
|
||||
{
|
||||
return interval.rate();
|
||||
}
|
||||
|
||||
const RunningAverageTimeInterval<sampleMethod>& stepInterval() const
|
||||
{
|
||||
return interval;
|
||||
}
|
||||
|
||||
Time::Interval lastStepInterval()
|
||||
{
|
||||
return interval.lastSample();
|
||||
}
|
||||
|
||||
const RunningAverage<double>& stepTime() const
|
||||
{
|
||||
return time;
|
||||
}
|
||||
|
||||
double getIntervalLerp() const
|
||||
{
|
||||
return interval.getLerp();
|
||||
}
|
||||
|
||||
private:
|
||||
RunningAverage<double> time;
|
||||
RunningAverageTimeInterval<sampleMethod> interval;
|
||||
};
|
||||
|
||||
|
||||
// A class that follows the pattern of WindowAverage, but keeps track of time spent in a cyclical task.
|
||||
template<Time::SampleMethod sampleMethod = Time::Benchmark>
|
||||
class WindowAverageDutyCycle
|
||||
{
|
||||
public:
|
||||
WindowAverageDutyCycle(Time::Interval maxWindow)
|
||||
: time(16)
|
||||
, interval(maxWindow)
|
||||
{
|
||||
}
|
||||
|
||||
void setMaxWindow(Time::Interval maxWindow)
|
||||
{
|
||||
interval.setMaxWindow(maxWindow);
|
||||
if (maxWindow.seconds() == 0.0)
|
||||
{
|
||||
// special case, release memory
|
||||
time.setMaxSamples(16);
|
||||
}
|
||||
};
|
||||
Time::Interval getMaxWindow() const
|
||||
{
|
||||
return interval.getMaxWindow();
|
||||
};
|
||||
|
||||
void sample(Time::Interval elapsedTime)
|
||||
{
|
||||
interval.sample();
|
||||
|
||||
// make sure time's buffer size tracks interval's buffer size.
|
||||
if (time.getMaxSamples() != interval.getCapacity())
|
||||
{
|
||||
time.setMaxSamples(interval.getCapacity());
|
||||
}
|
||||
|
||||
time.sample(elapsedTime.seconds());
|
||||
}
|
||||
|
||||
struct Stats
|
||||
{
|
||||
Stats(const typename WindowAverageTimeInterval<sampleMethod>::Stats& interval, const WindowAverage<double>::Stats& time, double dutyfraction)
|
||||
: interval(interval)
|
||||
, time(time)
|
||||
, dutyfraction(dutyfraction) {};
|
||||
typename WindowAverageTimeInterval<sampleMethod>::Stats interval;
|
||||
WindowAverage<double>::Stats time;
|
||||
double dutyfraction; // 1.0: duty time is 100% of interval time
|
||||
};
|
||||
|
||||
Stats getStats(size_t samples = ~0) const
|
||||
{
|
||||
typename WindowAverage<>::Stats timestats = time.getStats(samples);
|
||||
typename WindowAverageTimeInterval<sampleMethod>::Stats intervalstats = interval.getStats(samples);
|
||||
|
||||
double interval = intervalstats.average.seconds();
|
||||
return Stats(intervalstats, timestats, interval ? timestats.average / interval : 0);
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void iterTimes(F& f) const
|
||||
{
|
||||
time.iter(f);
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void iterIntervals(F& f) const
|
||||
{
|
||||
interval.iter(f);
|
||||
}
|
||||
|
||||
struct GTCounter
|
||||
{
|
||||
GTCounter(double gt)
|
||||
: c(0)
|
||||
, gtValue(gt) {};
|
||||
size_t c;
|
||||
double gtValue;
|
||||
void operator()(double dt)
|
||||
{
|
||||
if (dt > gtValue)
|
||||
c++;
|
||||
}
|
||||
};
|
||||
|
||||
size_t countTimesGreaterThan(Time::Interval dt) const
|
||||
{
|
||||
GTCounter count(dt.seconds());
|
||||
iterTimes(count);
|
||||
return count.c;
|
||||
}
|
||||
|
||||
size_t countIntervalsGreaterThan(Time::Interval dt) const
|
||||
{
|
||||
GTCounter count(dt.seconds());
|
||||
iterIntervals(count);
|
||||
return count.c;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
time.clear();
|
||||
interval.clear();
|
||||
}
|
||||
|
||||
size_t timesamples() const
|
||||
{
|
||||
return time.size();
|
||||
}
|
||||
|
||||
size_t intervalsamples() const
|
||||
{
|
||||
return interval.size();
|
||||
}
|
||||
|
||||
private:
|
||||
WindowAverage<double> time;
|
||||
WindowAverageTimeInterval<sampleMethod> interval;
|
||||
};
|
||||
|
||||
|
||||
// A thread-safe, lock-free DutyCycle meter
|
||||
template<int windowSeconds>
|
||||
class ActivityMeter
|
||||
{
|
||||
static const int bucketCount = windowSeconds * 1024;
|
||||
boost::array<char, bucketCount> buckets;
|
||||
Aya::atomic<int> currentTime;
|
||||
Aya::atomic<int> currentValue;
|
||||
Aya::atomic<int> totalValue;
|
||||
Aya::Time startTime;
|
||||
Aya::Time lastSampleTime;
|
||||
|
||||
public:
|
||||
ActivityMeter()
|
||||
: currentTime(-1)
|
||||
, currentValue(0)
|
||||
, totalValue(0)
|
||||
, startTime(Aya::Time::now<Time::Fast>())
|
||||
{
|
||||
for (size_t i = 0; i < buckets.size(); ++i)
|
||||
buckets[i] = 0;
|
||||
}
|
||||
|
||||
double averageValue()
|
||||
{
|
||||
updateBuckets();
|
||||
return (double)totalValue / (double)bucketCount;
|
||||
}
|
||||
|
||||
void increment()
|
||||
{
|
||||
updateBuckets();
|
||||
++currentValue;
|
||||
}
|
||||
|
||||
void decrement()
|
||||
{
|
||||
updateBuckets();
|
||||
--currentValue;
|
||||
}
|
||||
|
||||
void updateBuckets()
|
||||
{
|
||||
Aya::Time now = Time::now<Time::Fast>();
|
||||
if (lastSampleTime == now)
|
||||
return;
|
||||
|
||||
lastSampleTime = now;
|
||||
unsigned long newTime = ((unsigned long)(bucketCount * (lastSampleTime - startTime).seconds()));
|
||||
|
||||
unsigned long oldTime = currentTime.swap(newTime);
|
||||
|
||||
if (oldTime < newTime)
|
||||
{
|
||||
long newValue = currentValue;
|
||||
for (unsigned long i = oldTime + 1; i <= newTime; ++i)
|
||||
{
|
||||
int index = i % bucketCount;
|
||||
int oldBucketValue = buckets[index];
|
||||
|
||||
for (int j = 0; j < oldBucketValue; ++j)
|
||||
--totalValue;
|
||||
buckets[index] = (char)newValue;
|
||||
|
||||
for (int j = 0; j < newValue; ++j)
|
||||
++totalValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<int windowSeconds>
|
||||
class InvocationMeter
|
||||
{
|
||||
static const int bucketCount = windowSeconds * 1024;
|
||||
boost::array<char, bucketCount> buckets;
|
||||
Aya::atomic<int> currentTime;
|
||||
Aya::atomic<int> totalValue;
|
||||
Aya::Time startTime;
|
||||
Aya::Time lastSampleTime;
|
||||
|
||||
public:
|
||||
InvocationMeter()
|
||||
: currentTime(-1)
|
||||
, totalValue(0)
|
||||
, startTime(Aya::Time::now<Time::Fast>())
|
||||
{
|
||||
for (size_t i = 0; i < buckets.size(); ++i)
|
||||
buckets[i] = 0;
|
||||
}
|
||||
|
||||
double getTotalValuePerSecond()
|
||||
{
|
||||
updateBuckets(false);
|
||||
return totalValue / windowSeconds;
|
||||
}
|
||||
|
||||
void increment()
|
||||
{
|
||||
updateBuckets(true);
|
||||
}
|
||||
|
||||
void updateBuckets(bool increment)
|
||||
{
|
||||
Aya::Time now = Time::now<Time::Fast>();
|
||||
if (lastSampleTime == now)
|
||||
return;
|
||||
|
||||
lastSampleTime = now;
|
||||
unsigned long newTime = ((unsigned long)(bucketCount * (lastSampleTime - startTime).seconds()));
|
||||
unsigned long oldTime = currentTime.swap(newTime);
|
||||
// AYAASSERT(oldTime <= newTime);
|
||||
// if (oldTime != newTime)
|
||||
if (oldTime < newTime) // changed per Erik 11/18/09
|
||||
|
||||
{
|
||||
for (unsigned long i = oldTime + 1; i <= newTime; ++i)
|
||||
{
|
||||
int index = i % bucketCount;
|
||||
int oldBucketValue = buckets[index];
|
||||
|
||||
for (int j = 0; j < oldBucketValue; ++j)
|
||||
--totalValue;
|
||||
buckets[index] = 0;
|
||||
}
|
||||
}
|
||||
if (increment)
|
||||
{
|
||||
int newValue = 1;
|
||||
int newIndex = newTime % bucketCount;
|
||||
buckets[newIndex] = newValue;
|
||||
|
||||
for (int j = 0; j < newValue; ++j)
|
||||
++totalValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace Aya
|
||||
16
engine/core/src/SafeToLower.hpp
Normal file
16
engine/core/src/SafeToLower.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
inline void safeToLower(std::string& s)
|
||||
{
|
||||
for (unsigned i = 0; i < s.size(); ++i)
|
||||
{
|
||||
if (isupper(s[i]))
|
||||
{
|
||||
s[i] = tolower(s[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
53
engine/core/src/ScopedSingleton.hpp
Normal file
53
engine/core/src/ScopedSingleton.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
// #include "pdh.h"
|
||||
|
||||
#include "boost/weak_ptr.hpp"
|
||||
#include "boost/shared_ptr.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include "threadsafe.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
template<class T>
|
||||
class ScopedSingleton
|
||||
{
|
||||
private:
|
||||
static int& initCount()
|
||||
{
|
||||
static int initcount = 0;
|
||||
return initcount;
|
||||
}
|
||||
|
||||
protected:
|
||||
// use this to validate usage pattern.
|
||||
// some usage patterns will want to check that this doesn't
|
||||
// increase more than 1.
|
||||
static int getInitCount()
|
||||
{
|
||||
return initCount();
|
||||
}
|
||||
SAFE_STATIC(Aya::spin_mutex, sync)
|
||||
SAFE_STATIC(boost::weak_ptr<T>, s_instance)
|
||||
public:
|
||||
static boost::shared_ptr<T> getInstance()
|
||||
{
|
||||
Aya::spin_mutex::scoped_lock lock(sync());
|
||||
|
||||
shared_ptr<T> result = s_instance().lock();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
initCount()++;
|
||||
result = shared_ptr<T>(new T());
|
||||
s_instance() = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
static boost::shared_ptr<T> getInstanceOptional()
|
||||
{
|
||||
return s_instance().lock();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
14
engine/core/src/SelectState.hpp
Normal file
14
engine/core/src/SelectState.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
enum SelectState
|
||||
{
|
||||
SELECT_NORMAL,
|
||||
SELECT_LIMIT,
|
||||
SELECT_HOVER,
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
112
engine/core/src/SimpleJSON.cpp
Normal file
112
engine/core/src/SimpleJSON.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "SimpleJSON.hpp"
|
||||
#include "rapidjson/document.h"
|
||||
#include <sstream>
|
||||
|
||||
// used for the old parser
|
||||
#ifndef AYA_BOOTSTRAPPER_MAC
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#endif
|
||||
|
||||
void SimpleJSON::ReadFromStream(const char* stream)
|
||||
{
|
||||
rapidjson::Document root;
|
||||
root.Parse<0>(stream);
|
||||
if (root.GetParseError())
|
||||
{
|
||||
// RapidJSON actually crashes if you attempt to iterate over an invalid document.
|
||||
size_t errOffset = root.GetErrorOffset();
|
||||
std::string errMessage = "RapidJSON error " + root.GetParseError();
|
||||
std::stringstream msg("SimpleJson, @");
|
||||
msg << errOffset << ": " << errMessage;
|
||||
_error = true;
|
||||
_errorString = std::string(msg.str());
|
||||
return;
|
||||
}
|
||||
for (rapidjson::Value::MemberIterator it = root.MemberBegin(); it != root.MemberEnd(); ++it)
|
||||
{
|
||||
std::string valueName(it->name.GetString());
|
||||
std::stringstream valueData;
|
||||
|
||||
// (only IsString was originally supported by the original SimpleJSON)
|
||||
if (it->value.IsString())
|
||||
{
|
||||
valueData << it->value.GetString();
|
||||
}
|
||||
else if (it->value.IsInt())
|
||||
{
|
||||
valueData << it->value.GetInt();
|
||||
}
|
||||
else if (it->value.IsBool())
|
||||
{
|
||||
valueData << (it->value.GetBool() ? "True" : "False");
|
||||
}
|
||||
else
|
||||
{
|
||||
// error
|
||||
continue;
|
||||
}
|
||||
parser dataParser = _propValues[valueName];
|
||||
if (dataParser != NULL)
|
||||
{
|
||||
dataParser(valueData.str().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
DefaultHandler(valueName, valueData.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleJSON::ParseBool(const char* value)
|
||||
{
|
||||
return (strcmp(value, "true") == 0 || strcmp(value, "True") == 0) ? true : false;
|
||||
}
|
||||
|
||||
|
||||
// Below is the old version that will be kept around for a while.
|
||||
//
|
||||
|
||||
static const char* ValueEnclosure = "\"";
|
||||
static const char LineBreak = 10;
|
||||
|
||||
static bool needTrimSymbol(char c)
|
||||
{
|
||||
return (c == ValueEnclosure[0]) || c == LineBreak || c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
||||
}
|
||||
|
||||
#ifdef AYA_BOOTSTRAPPER_MAC
|
||||
static void trim_str_left(std::string& s)
|
||||
{
|
||||
std::string::iterator iter = s.begin();
|
||||
for (; iter != s.end(); ++iter)
|
||||
{
|
||||
if (!needTrimSymbol(*iter))
|
||||
break;
|
||||
}
|
||||
s.erase(s.begin(), iter);
|
||||
}
|
||||
static void trim_str_right(std::string& s)
|
||||
{
|
||||
std::string::iterator iter = s.end();
|
||||
for (; iter != s.begin();)
|
||||
{
|
||||
if (!needTrimSymbol(*(--iter)))
|
||||
{
|
||||
++iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s.erase(iter, s.end());
|
||||
}
|
||||
static void trim_str(std::string& s)
|
||||
{
|
||||
try
|
||||
{
|
||||
trim_str_left(s);
|
||||
trim_str_right(s);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
108
engine/core/src/SimpleJSON.hpp
Normal file
108
engine/core/src/SimpleJSON.hpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class SimpleJSON;
|
||||
typedef void (*parser)(const char* stream);
|
||||
|
||||
#define START_DATA_MAP(className) \
|
||||
static className* _thisPtr; \
|
||||
void Init(); \
|
||||
className() \
|
||||
{ \
|
||||
_thisPtr = this; \
|
||||
Init(); \
|
||||
} \
|
||||
virtual ~className() {}
|
||||
|
||||
#define END_DATA_MAP()
|
||||
|
||||
#define DATA_MAP_IMPL_START(className) \
|
||||
className* className::_thisPtr = NULL; \
|
||||
void className::Init() \
|
||||
{
|
||||
|
||||
#define DATA_MAP_IMPL_END() }
|
||||
|
||||
#define DECLARE_DATA_INT(name) \
|
||||
private: \
|
||||
int _prop##name; \
|
||||
\
|
||||
public: \
|
||||
int GetValue##name() const \
|
||||
{ \
|
||||
return _prop##name; \
|
||||
} \
|
||||
static void ReadValue##name(const char* stream) \
|
||||
{ \
|
||||
_thisPtr->_prop##name = atoi(stream); \
|
||||
}
|
||||
|
||||
#define DECLARE_DATA_BOOL(name) \
|
||||
private: \
|
||||
bool _prop##name; \
|
||||
\
|
||||
public: \
|
||||
bool GetValue##name() const \
|
||||
{ \
|
||||
return _prop##name; \
|
||||
} \
|
||||
static void ReadValue##name(const char* stream) \
|
||||
{ \
|
||||
_thisPtr->_prop##name = ParseBool(stream); \
|
||||
}
|
||||
|
||||
#define DECLARE_DATA_STRING(name) \
|
||||
private: \
|
||||
std::string _prop##name; \
|
||||
\
|
||||
public: \
|
||||
const char* GetValue##name() const \
|
||||
{ \
|
||||
return _prop##name.c_str(); \
|
||||
} \
|
||||
static void ReadValue##name(const char* stream) \
|
||||
{ \
|
||||
_thisPtr->_prop##name = std::string(stream); \
|
||||
}
|
||||
|
||||
|
||||
#define IMPL_DATA(name, def) \
|
||||
_prop##name = def; \
|
||||
_propValues[std::string(#name)] = ReadValue##name;
|
||||
|
||||
class SimpleJSON
|
||||
{
|
||||
protected:
|
||||
std::map<std::string, parser> _propValues;
|
||||
bool _error;
|
||||
std::string _errorString;
|
||||
|
||||
public:
|
||||
SimpleJSON()
|
||||
: _error(false){};
|
||||
virtual ~SimpleJSON(){};
|
||||
|
||||
static bool ParseBool(const char* value);
|
||||
|
||||
void ReadFromStream(const char* stream);
|
||||
|
||||
bool GetError() const
|
||||
{
|
||||
return _error;
|
||||
}
|
||||
void ClearError()
|
||||
{
|
||||
_error = false;
|
||||
}
|
||||
std::string GetErrorString() const
|
||||
{
|
||||
return _errorString;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool DefaultHandler(const std::string& valueName, const std::string& valueData)
|
||||
{
|
||||
return false;
|
||||
};
|
||||
};
|
||||
15
engine/core/src/StreamHelpers.cpp
Normal file
15
engine/core/src/StreamHelpers.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "StreamHelpers.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
void readStreamIntoString(std::istream& stream, std::string& content)
|
||||
{
|
||||
size_t contentLength = 0;
|
||||
stream.seekg(0, std::ios::end);
|
||||
contentLength = static_cast<size_t>(stream.tellg());
|
||||
stream.seekg(0, std::ios::beg);
|
||||
|
||||
content = std::string(contentLength, '\0');
|
||||
stream.read(&content[0], contentLength);
|
||||
}
|
||||
} // namespace Aya
|
||||
7
engine/core/src/StreamHelpers.hpp
Normal file
7
engine/core/src/StreamHelpers.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include <istream>
|
||||
#include <string>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
void readStreamIntoString(std::istream& stream, std::string& content);
|
||||
} // namespace Aya
|
||||
44
engine/core/src/StringConv.cpp
Normal file
44
engine/core/src/StringConv.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// d9mz - might be something wrong with my sdk, but this is needed to compile shadercompiler - it sucks
|
||||
#ifndef PTRDIFF_MAX
|
||||
#include "StringConv.hpp"
|
||||
#else
|
||||
#include "StringConv.hpp"
|
||||
#endif
|
||||
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
#if !defined(__linux) && !defined(__APPLE__)
|
||||
// convert wstring to UTF-8 string
|
||||
std::string utf8_encode(const std::wstring& path)
|
||||
{
|
||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &path[0], (int)path.size(), NULL, 0, NULL, NULL);
|
||||
std::string tgt(size_needed, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, &path[0], (int)path.size(), &tgt[0], size_needed, NULL, NULL);
|
||||
return tgt;
|
||||
}
|
||||
|
||||
// convert UTF-8 string to wstring
|
||||
std::wstring utf8_decode(const std::string& path)
|
||||
{
|
||||
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &path[0], (int)path.size(), NULL, 0);
|
||||
std::wstring tgt(size_needed, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, &path[0], (int)path.size(), &tgt[0], size_needed);
|
||||
return tgt;
|
||||
}
|
||||
#else
|
||||
std::string utf8_encode(const std::string& path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
std::string utf8_decode(const std::string& path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Aya
|
||||
15
engine/core/src/StringConv.hpp
Normal file
15
engine/core/src/StringConv.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
typedef std::wstring SysPathString;
|
||||
#else // assume we're on Linux / Unix, which usually don't require conversions
|
||||
typedef std::string SysPathString;
|
||||
#endif
|
||||
|
||||
std::string utf8_encode(const SysPathString& path);
|
||||
SysPathString utf8_decode(const std::string& path);
|
||||
} // namespace Aya
|
||||
191
engine/core/src/SystemUtil.cpp
Normal file
191
engine/core/src/SystemUtil.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#include "SystemUtil.hpp"
|
||||
#include "StreamHelpers.hpp"
|
||||
#include "FastLog.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#ifdef __ANDROID_API__
|
||||
#include <android/api-level.h>
|
||||
#endif
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <glad/gl.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <setupapi.h>
|
||||
|
||||
#include <initguid.h>
|
||||
#include <d3d9.h>
|
||||
#include "ddraw.h"
|
||||
#include <d3d11.h>
|
||||
#include <dxgi1_4.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace SystemUtil
|
||||
{
|
||||
std::string mOSVersion; // Set in JNIGLActivity.cpp
|
||||
std::string mDeviceName;
|
||||
|
||||
std::string getCPUMake()
|
||||
{
|
||||
#ifdef __arm_
|
||||
return "ARM";
|
||||
#elif __aarch64__
|
||||
return "AArch64";
|
||||
#elif __i386__ || __amd64__
|
||||
return "Intel";
|
||||
#else
|
||||
#error Unsupported platform.
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t getCPUSpeed()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t getCPULogicalCount()
|
||||
{
|
||||
return getCPUCoreCount();
|
||||
}
|
||||
|
||||
uint64_t getCPUCoreCount()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO sysinfo;
|
||||
GetSystemInfo(&sysinfo);
|
||||
return sysinfo.dwNumberOfProcessors;
|
||||
#else
|
||||
return sysconf(_SC_NPROCESSORS_CONF);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t getCPUPhysicalCount()
|
||||
{
|
||||
return getCPUCoreCount();
|
||||
}
|
||||
|
||||
bool isCPU64Bit()
|
||||
{
|
||||
#ifdef __arm64__ || __amd64__ || _WIN64
|
||||
return true;
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
BOOL result = false;
|
||||
return IsWow64Process(GetCurrentProcess(), &result) && result;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t getMBSysRAM()
|
||||
{
|
||||
return 16 * 1024;
|
||||
}
|
||||
|
||||
uint64_t getMBSysAvailableRAM()
|
||||
{
|
||||
return getMBSysRAM();
|
||||
}
|
||||
|
||||
uint64_t getVideoMemory()
|
||||
{
|
||||
GLint total_mem_kb = 0;
|
||||
// return 8gb
|
||||
return 8 * 1024 * 1024;
|
||||
}
|
||||
|
||||
std::string osPlatform()
|
||||
{
|
||||
#ifdef __ANDROID_API__
|
||||
return "Android";
|
||||
#elif _WIN32
|
||||
return "Win32";
|
||||
#elif __APPLE__
|
||||
return "Apple";
|
||||
#else
|
||||
return "Linux";
|
||||
#endif
|
||||
}
|
||||
|
||||
int osPlatformId()
|
||||
{
|
||||
#ifdef __ANDROID_API__
|
||||
return __ANDROID_API__;
|
||||
#elif _WIN32
|
||||
return VER_PLATFORM_WIN32_NT;
|
||||
#else
|
||||
return 0x7999999999;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string osVer()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
OSVERSIONINFO osvi = {0};
|
||||
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
||||
#pragma warning(disable : 4996 28159)
|
||||
GetVersionEx(&osvi);
|
||||
return Aya::format("%d.%d.%d.%d", osvi.dwOSVersionInfoSize, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber);
|
||||
#else
|
||||
return mOSVersion;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string deviceName()
|
||||
{
|
||||
#ifndef __ANDROID_API__
|
||||
return "PC";
|
||||
#else
|
||||
return Aya::SystemUtil::mDeviceName;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getGPUMake()
|
||||
{
|
||||
#ifdef __ANDROID_API__
|
||||
return "Android";
|
||||
#elif _WIN32
|
||||
|
||||
DISPLAY_DEVICE displayDevice;
|
||||
ZeroMemory(&displayDevice, sizeof(displayDevice));
|
||||
displayDevice.cb = sizeof(displayDevice);
|
||||
EnumDisplayDevices(NULL, 0, &displayDevice, 0);
|
||||
|
||||
return std::string(displayDevice.DeviceString);
|
||||
#else
|
||||
return "Some Linux GPU";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getMaxRes()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string getExecutablePath()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char buffer[MAX_PATH];
|
||||
GetModuleFileNameA(NULL, buffer, MAX_PATH);
|
||||
return std::filesystem::path(buffer).parent_path().string();
|
||||
#else
|
||||
char buffer[PATH_MAX] = {0};
|
||||
readlink("/proc/self/exe", buffer, sizeof(buffer));
|
||||
return std::filesystem::path(buffer).parent_path();
|
||||
#endif
|
||||
}
|
||||
} // namespace SystemUtil
|
||||
} // namespace Aya
|
||||
42
engine/core/src/SystemUtil.hpp
Normal file
42
engine/core/src/SystemUtil.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace SystemUtil
|
||||
{
|
||||
/// CPU Related
|
||||
std::string getCPUMake();
|
||||
uint64_t getCPUSpeed();
|
||||
uint64_t getCPULogicalCount();
|
||||
uint64_t getCPUCoreCount();
|
||||
uint64_t getCPUPhysicalCount();
|
||||
bool isCPU64Bit();
|
||||
|
||||
/// Memory Related
|
||||
uint64_t getMBSysRAM();
|
||||
uint64_t getMBSysAvailableRAM();
|
||||
uint64_t getVideoMemory();
|
||||
|
||||
/// OS Related
|
||||
std::string osPlatform();
|
||||
int osPlatformId();
|
||||
std::string osVer();
|
||||
std::string deviceName();
|
||||
|
||||
/// GPU Related
|
||||
std::string getGPUMake();
|
||||
|
||||
// Display Resolution
|
||||
std::string getMaxRes();
|
||||
|
||||
// Directory storing the current executing binary
|
||||
std::string getExecutablePath();
|
||||
} // namespace SystemUtil
|
||||
} // namespace Aya
|
||||
317
engine/core/src/TaskScheduler.Job.cpp
Normal file
317
engine/core/src/TaskScheduler.Job.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
|
||||
#include "TaskScheduler.Job.hpp"
|
||||
#include "Coordinator.hpp"
|
||||
#include "Log.hpp"
|
||||
#include "FastLog.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
LOGGROUP(TaskSchedulerSteps)
|
||||
|
||||
#if HANG_DETECTION
|
||||
#include <../../App/include/Utility/standardout.h>
|
||||
#define STEPTIME_SAMPLE_INTEVAL 60.0
|
||||
double Aya::TaskScheduler::Job::stepTimeThreshold = 0.0;
|
||||
#endif
|
||||
|
||||
TaskScheduler::Job::SleepAdjustMethod TaskScheduler::Job::sleepAdjustMethod(AverageInterval);
|
||||
double Aya::TaskScheduler::Job::throttledSleepTime = 0.01;
|
||||
|
||||
double TaskScheduler::Job::averageDutyCycle() const
|
||||
{
|
||||
return dutyCycle.dutyCycle();
|
||||
}
|
||||
|
||||
double TaskScheduler::Job::averageSleepRate() const
|
||||
{
|
||||
return sleepRate.value();
|
||||
}
|
||||
|
||||
double TaskScheduler::Job::averageStepsPerSecond() const
|
||||
{
|
||||
return dutyCycle.stepInterval().rate();
|
||||
}
|
||||
|
||||
double TaskScheduler::Job::averageStepTime() const
|
||||
{
|
||||
return dutyCycle.stepTime().value();
|
||||
}
|
||||
|
||||
Time::Interval TaskScheduler::Job::getSleepingTime() const
|
||||
{
|
||||
if (state != Sleeping)
|
||||
return Time::Interval::zero();
|
||||
else
|
||||
return Time::now<Time::Fast>() - timeofLastSleep;
|
||||
}
|
||||
|
||||
double TaskScheduler::Job::averageError() const
|
||||
{
|
||||
return runningAverageError.value();
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::Job::removeCoordinator(class boost::shared_ptr<class Aya::Tasks::Coordinator> coordinator)
|
||||
{
|
||||
coordinator->onRemoved(this);
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(coordinatorMutex);
|
||||
coordinators.erase(std::find(coordinators.begin(), coordinators.end(), coordinator));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::Job::addCoordinator(class boost::shared_ptr<class Aya::Tasks::Coordinator> coordinator)
|
||||
{
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(coordinatorMutex);
|
||||
coordinators.push_back(coordinator);
|
||||
}
|
||||
coordinator->onAdded(this);
|
||||
}
|
||||
|
||||
bool TaskScheduler::Job::isDisabled()
|
||||
{
|
||||
if (coordinators.size() == 0)
|
||||
return false;
|
||||
|
||||
Aya::mutex::scoped_lock lock(coordinatorMutex);
|
||||
// TODO: Write more efficiently without boost bind
|
||||
return std::find_if(coordinators.begin(), coordinators.end(), boost::bind(&Tasks::Coordinator::isInhibited, _1, this)) != coordinators.end();
|
||||
}
|
||||
|
||||
const double lerpJob = 0.05;
|
||||
extern Aya::Time::Interval maxDutyCycleWindow;
|
||||
|
||||
TaskScheduler::Job::Job(const char* name, shared_ptr<TaskScheduler::Arbiter> arbiter, Time::Interval stepBudget)
|
||||
: name(name)
|
||||
, sharedArbiter(arbiter)
|
||||
, weakArbiter(boost::weak_ptr<Arbiter>())
|
||||
, baldArbiter(NULL)
|
||||
, state(Unknown)
|
||||
, sleepRate(lerpJob)
|
||||
, dutyCycle(lerpJob, 8)
|
||||
, runningAverageError(lerpJob)
|
||||
, timespanOfLastStep(0.0)
|
||||
, isRemoveRequested(false)
|
||||
, dutyCycleWindow(maxDutyCycleWindow)
|
||||
, stepBudget(stepBudget)
|
||||
, overStepTimeThresholdCount(0)
|
||||
, allotedConcurrency(-1)
|
||||
, cyclicExecutive(false)
|
||||
, cyclicPriority(CyclicExecutiveJobPriority_Default)
|
||||
{
|
||||
FASTLOG2(FLog::TaskSchedulerInit, "Job Created - this(%p) arbiter(%p)", this, arbiter.get());
|
||||
FASTLOGS(FLog::TaskSchedulerInit, "JobName(%s)", name);
|
||||
}
|
||||
|
||||
TaskScheduler::Job::~Job()
|
||||
{
|
||||
AYAASSERT(!TaskScheduler::SleepingHook::is_linked());
|
||||
AYAASSERT(!TaskScheduler::WaitingHook::is_linked());
|
||||
while (coordinators.size() > 0)
|
||||
{
|
||||
coordinators.back()->onRemoved(this);
|
||||
coordinators.pop_back();
|
||||
}
|
||||
|
||||
FASTLOG1(FLog::TaskSchedulerInit, "Job Destroyed - this(%p)", this);
|
||||
FASTLOGS(FLog::TaskSchedulerInit, "JobName(%s)", name);
|
||||
}
|
||||
|
||||
// Use this to generate the error function if you just want to try to track the desiredHz
|
||||
TaskScheduler::Job::Error TaskScheduler::Job::computeStandardError(const Stats& stats, double desiredHz)
|
||||
{
|
||||
return stats.timespanSinceLastStep.seconds() * desiredHz;
|
||||
}
|
||||
|
||||
// Same as computeStandardError but incorporates sleeping in Cyclic Executive.
|
||||
TaskScheduler::Job::Error TaskScheduler::Job::computeStandardErrorCyclicExecutiveSleeping(const Stats& stats, double desiredHz)
|
||||
{
|
||||
TaskScheduler::Job::Error error = computeStandardError(stats, desiredHz);
|
||||
if (Aya::TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive)
|
||||
{
|
||||
// Waking up at error >= 1.0 means that a task always runs late. In practice
|
||||
// there is always scheduling jitter, however in the spirit of minimally
|
||||
// intrusive changes allow the task to wake up just a tiny bit early.
|
||||
if (error.error < 0.98)
|
||||
{
|
||||
error.error = 0.0; // Sleep until ready.
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
Time::Interval TaskScheduler::Job::computeStandardSleepTime(const Stats& stats, double desiredHz)
|
||||
{
|
||||
// bool schedulerIsCyclicExecutive = Aya::TaskScheduler::singleton().isCyclicExecutive();
|
||||
// AYAASSERT( !schedulerIsCyclicExecutive || cyclicExecutive || desiredHz <= 5 );
|
||||
|
||||
shared_ptr<Arbiter> ar(getArbiter());
|
||||
Time::Interval minTime = ar && ar->isThrottled() ? Time::Interval(throttledSleepTime) : Time::Interval::zero();
|
||||
|
||||
const double interval = 1.0 / desiredHz;
|
||||
|
||||
switch (sleepAdjustMethod)
|
||||
{
|
||||
case AverageInterval:
|
||||
// If we're not being called as frequently as we'd like, then don't sleep
|
||||
if (dutyCycle.stepInterval().value().seconds() > 1.05 * interval)
|
||||
return minTime;
|
||||
break;
|
||||
|
||||
case LastSample:
|
||||
if (dutyCycle.lastStepInterval().seconds() > 1.05 * interval)
|
||||
return minTime;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Time::Interval result = Time::Interval(interval) - stats.timespanSinceLastStep;
|
||||
if (result < minTime)
|
||||
return minTime;
|
||||
return result;
|
||||
}
|
||||
|
||||
TaskScheduler::Job::Stats::Stats(Job& job, Time now)
|
||||
{
|
||||
timeNow = now;
|
||||
timespanSinceLastStep = now - job.timeofLastStep;
|
||||
timespanOfLastStep = job.timespanOfLastStep;
|
||||
}
|
||||
|
||||
void TaskScheduler::Job::startWaiting()
|
||||
{
|
||||
state = Waiting;
|
||||
}
|
||||
|
||||
void TaskScheduler::Job::startSleeping()
|
||||
{
|
||||
state = Sleeping;
|
||||
timeofLastSleep = Time::now<Time::Fast>();
|
||||
}
|
||||
|
||||
void TaskScheduler::Job::updateWakeTime()
|
||||
{
|
||||
Time now = Time::now<Time::Fast>();
|
||||
Job::Stats stats(*this, now);
|
||||
wakeTime = now + sleepTime(stats);
|
||||
}
|
||||
|
||||
void TaskScheduler::Job::updateError(const Time& now)
|
||||
{
|
||||
Job::Stats stats(*this, now);
|
||||
currentError = error(stats);
|
||||
AYAASSERT(currentError.error < std::numeric_limits<double>::max());
|
||||
if (currentError.error > 0)
|
||||
runningAverageError.sample(currentError.error);
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::Job::notifyCoordinatorsPreStep()
|
||||
{
|
||||
if (coordinators.size() > 0)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(coordinatorMutex);
|
||||
std::for_each(coordinators.begin(), coordinators.end(), boost::bind(&Tasks::Coordinator::onPreStep, _1, this));
|
||||
}
|
||||
}
|
||||
|
||||
void TaskScheduler::Job::preStep()
|
||||
{
|
||||
state = Running;
|
||||
|
||||
FASTLOG4(FLog::TaskSchedulerRun, "JobStart. this: %p arbiter: %p time: %u error: %d", this, getArbiter().get(),
|
||||
(unsigned)stepStartTime.timestampSeconds(), currentError.urgent ? -1 : (int)currentError.error);
|
||||
FASTLOGS(FLog::TaskSchedulerRun, "JobStart %s", name.c_str());
|
||||
|
||||
|
||||
shared_ptr<Arbiter> ar(getArbiter());
|
||||
if (ar)
|
||||
ar->preStep(this);
|
||||
}
|
||||
|
||||
void TaskScheduler::Job::postStep(StepResult result)
|
||||
{
|
||||
state = Unknown;
|
||||
timeofLastStep = stepStartTime;
|
||||
const Time now = Time::now<Time::Fast>();
|
||||
timespanOfLastStep = now - stepStartTime;
|
||||
|
||||
// TODO: extract stepTime from dutyCycle?
|
||||
dutyCycle.sample(timespanOfLastStep);
|
||||
|
||||
if (!stepBudget.isZero() && timespanOfLastStep > stepBudget)
|
||||
{
|
||||
// We were over budget
|
||||
}
|
||||
|
||||
#if HANG_DETECTION
|
||||
if ((stepTimeThreshold > 0) && (timespanOfLastStep.seconds() > stepTimeThreshold))
|
||||
{
|
||||
overStepTimeThresholdCount++;
|
||||
StandardOut::singleton()->printf(MESSAGE_WARNING, "TaskScheduler::Job: %s step time over threshold", name.c_str());
|
||||
|
||||
// send to log
|
||||
if (Aya::Log::current() && ((Time::now<Time::Fast>() - stepTimeSampleTime).seconds() > STEPTIME_SAMPLE_INTEVAL))
|
||||
{
|
||||
std::string msg = "TaskScheduler::Job: " + name + " step time over threshold.";
|
||||
Aya::Log::current()->writeEntry(Aya::Log::Error, msg.c_str());
|
||||
|
||||
stepTimeSampleTime = Time::now<Time::Fast>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dutyCycleWindow.getMaxWindow().seconds() > 0)
|
||||
{
|
||||
dutyCycleWindow.sample(timespanOfLastStep);
|
||||
}
|
||||
|
||||
FASTLOG3(FLog::TaskSchedulerRun, "JobStop. This: %p arbiter: %p time: %u", this, getArbiter().get(), (unsigned)now.timestampSeconds());
|
||||
FASTLOGS(FLog::TaskSchedulerRun, "JobStop %s", name.c_str());
|
||||
|
||||
shared_ptr<Arbiter> ar(getArbiter());
|
||||
if (ar)
|
||||
ar->postStep(this);
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::Job::notifyCoordinatorsPostStep()
|
||||
{
|
||||
if (coordinators.size() > 0)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(coordinatorMutex);
|
||||
std::for_each(coordinators.begin(), coordinators.end(), boost::bind(&Tasks::Coordinator::onPostStep, _1, this));
|
||||
}
|
||||
}
|
||||
|
||||
void TaskScheduler::Job::updatePriority()
|
||||
{
|
||||
switch (TaskScheduler::priorityMethod)
|
||||
{
|
||||
case FIFO:
|
||||
priority = 1.0;
|
||||
break;
|
||||
|
||||
case LastError:
|
||||
priority = currentError.error;
|
||||
break;
|
||||
|
||||
case AccumulatedError:
|
||||
double avg = averageError();
|
||||
if (avg > 0)
|
||||
priority = avg;
|
||||
else
|
||||
priority = currentError.error;
|
||||
|
||||
priority *= getPriorityFactor();
|
||||
|
||||
double st = std::max(0.01, averageDutyCycle());
|
||||
priority /= st;
|
||||
break;
|
||||
}
|
||||
}
|
||||
256
engine/core/src/TaskScheduler.Job.hpp
Normal file
256
engine/core/src/TaskScheduler.Job.hpp
Normal file
@@ -0,0 +1,256 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Countable.hpp"
|
||||
#include "TaskScheduler.hpp"
|
||||
#include "boost/enable_shared_from_this.hpp"
|
||||
#include "boost/weak_ptr.hpp"
|
||||
|
||||
#define HANG_DETECTION 0
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Tasks
|
||||
{
|
||||
class Coordinator;
|
||||
}
|
||||
|
||||
enum CyclicExecutiveJobPriority
|
||||
{
|
||||
CyclicExecutiveJobPriority_EarlyRendering,
|
||||
CyclicExecutiveJobPriority_Network_ReceiveIncoming,
|
||||
CyclicExecutiveJobPriority_Network_ProcessIncoming,
|
||||
CyclicExecutiveJobPriority_Default,
|
||||
CyclicExecutiveJobPriority_Physics,
|
||||
CyclicExecutiveJobPriority_Heartbeat,
|
||||
CyclicExecutiveJobPriority_Network_ProcessOutgoing,
|
||||
CyclicExecutiveJobPriority_Render
|
||||
};
|
||||
|
||||
class AyaBaseClass TaskScheduler::Job
|
||||
: boost::noncopyable
|
||||
, public boost::enable_shared_from_this<Job>
|
||||
, Aya::Diagnostics::Countable<Job>
|
||||
, public TaskScheduler::SleepingHook
|
||||
, public TaskScheduler::WaitingHook
|
||||
{
|
||||
friend class TaskScheduler;
|
||||
boost::weak_ptr<Thread> lastThreadUsed; // attempt to re-use a thread for thread affinity
|
||||
|
||||
Aya::mutex coordinatorMutex;
|
||||
std::vector<boost::shared_ptr<Aya::Tasks::Coordinator>> coordinators;
|
||||
boost::shared_ptr<TaskScheduler::Arbiter> const sharedArbiter;
|
||||
boost::weak_ptr<TaskScheduler::Arbiter> const weakArbiter;
|
||||
TaskScheduler::Arbiter* const baldArbiter;
|
||||
|
||||
// stepBudget of 0 means no budget
|
||||
public:
|
||||
const std::string name;
|
||||
static double throttledSleepTime;
|
||||
const Time::Interval stepBudget;
|
||||
Time stepStartTime;
|
||||
int allotedConcurrency;
|
||||
bool cyclicExecutive;
|
||||
CyclicExecutiveJobPriority cyclicPriority;
|
||||
|
||||
#if HANG_DETECTION
|
||||
static double
|
||||
stepTimeThreshold; // in seconds. A count is incremented when a job's stepTime is over this value. A value of 0 means disable counting.
|
||||
Time stepTimeSampleTime;
|
||||
#endif
|
||||
|
||||
struct Stats
|
||||
{
|
||||
Stats(Job& job, Time now);
|
||||
Time timeNow;
|
||||
Time::Interval timespanSinceLastStep;
|
||||
Time::Interval timespanOfLastStep;
|
||||
};
|
||||
|
||||
struct Error
|
||||
{
|
||||
double error;
|
||||
|
||||
bool urgent; // experimental feature to prevent UI deadlocks (Essentially bumps the Job up in the priority queue)
|
||||
|
||||
Error()
|
||||
: error(0.0)
|
||||
, urgent(false)
|
||||
{
|
||||
}
|
||||
Error(double error)
|
||||
: error(error)
|
||||
, urgent(false)
|
||||
{
|
||||
}
|
||||
bool isDefault()
|
||||
{
|
||||
return error == 0.0 && !urgent;
|
||||
} // Generic jobs are flagged as urgent for Cyclic Executive.
|
||||
};
|
||||
|
||||
void addCoordinator(shared_ptr<Tasks::Coordinator> coordinator);
|
||||
void removeCoordinator(shared_ptr<Tasks::Coordinator> coordinator);
|
||||
|
||||
Time::Interval getSleepingTime() const; // returns > 0 if the job is asleep
|
||||
double averageDutyCycle() const;
|
||||
const RunningAverageDutyCycle<>& getStepStats() const
|
||||
{
|
||||
return dutyCycle;
|
||||
}
|
||||
double averageSleepRate() const;
|
||||
double averageStepsPerSecond() const;
|
||||
double averageStepTime() const;
|
||||
double averageError() const;
|
||||
bool isRunning() const
|
||||
{
|
||||
return state == Running;
|
||||
}
|
||||
bool isDisabled();
|
||||
|
||||
typedef enum
|
||||
{
|
||||
None,
|
||||
LastSample,
|
||||
AverageInterval
|
||||
} SleepAdjustMethod;
|
||||
static SleepAdjustMethod sleepAdjustMethod;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
Unknown,
|
||||
Sleeping,
|
||||
Waiting,
|
||||
Running
|
||||
} State;
|
||||
State getState() const
|
||||
{
|
||||
return state;
|
||||
}
|
||||
Time getWakeTime() const
|
||||
{
|
||||
return wakeTime;
|
||||
}
|
||||
Time::Interval getWake() const
|
||||
{
|
||||
return wakeTime - Time::now<Time::Fast>();
|
||||
}
|
||||
double getPriority() const
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
std::string getDebugName() const
|
||||
{
|
||||
shared_ptr<Arbiter> ar(getArbiter());
|
||||
if (ar)
|
||||
return Aya::format("%s:%s", ar->arbiterName().c_str(), name.c_str());
|
||||
else
|
||||
return name;
|
||||
}
|
||||
|
||||
static bool isLowerWakeTime(const TaskScheduler::Job& job1, const TaskScheduler::Job& job2)
|
||||
{
|
||||
return job1.wakeTime < job2.wakeTime;
|
||||
}
|
||||
|
||||
WindowAverageDutyCycle<>& getDutyCycleWindow()
|
||||
{
|
||||
return dutyCycleWindow;
|
||||
}
|
||||
const WindowAverageDutyCycle<>& getDutyCycleWindow() const
|
||||
{
|
||||
return dutyCycleWindow;
|
||||
}
|
||||
|
||||
protected:
|
||||
Job(const char* name, shared_ptr<TaskScheduler::Arbiter> arbiter, Time::Interval stepBudget = Time::Interval(0));
|
||||
virtual ~Job();
|
||||
|
||||
public:
|
||||
inline const shared_ptr<TaskScheduler::Arbiter>& getArbiter() const
|
||||
{
|
||||
return sharedArbiter;
|
||||
}
|
||||
inline bool hasArbiter(TaskScheduler::Arbiter* test) const
|
||||
{
|
||||
return sharedArbiter.get() == test || sharedArbiter->getSyncronizationArbiter() == test;
|
||||
};
|
||||
|
||||
inline static bool haveDifferentArbiters(const Job* job1, const Job* job2)
|
||||
{
|
||||
Arbiter* a1 = job1->sharedArbiter.get();
|
||||
if (a1)
|
||||
a1 = a1->getSyncronizationArbiter();
|
||||
|
||||
Arbiter* a2 = job2->sharedArbiter.get();
|
||||
if (a2)
|
||||
a2 = a2->getSyncronizationArbiter();
|
||||
|
||||
return a1 != a2;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// Abstract functions
|
||||
private:
|
||||
// sleepTime>0 means the job will sleep
|
||||
virtual Time::Interval sleepTime(const Stats& stats) = 0;
|
||||
|
||||
// error==0 means the job won't be scheduled
|
||||
virtual Error error(const Stats& stats) = 0;
|
||||
|
||||
// Used in Cyclic Executive to decide if we should re-run entire TaskScheduler loop
|
||||
// This is used to let LegacyLocks clear in Studio when waiting for Render job.
|
||||
virtual bool tryJobAgain()
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
// Used to determine which job gets priority. The priority is multiplied by this factor
|
||||
virtual double getPriorityFactor() = 0;
|
||||
|
||||
// The Job is being asked to step.
|
||||
virtual StepResult step(const Stats& stats) = 0;
|
||||
|
||||
virtual int getDesiredConcurrencyCount() const
|
||||
{
|
||||
// return >1 if the job intends to do parallel work (using multiple threads)
|
||||
// During the step function, query the number of threads alloted by checking
|
||||
// allotedConcurrency
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Use this to generate the error function if you just want to try to track the desiredHz
|
||||
Error computeStandardError(const Stats& stats, double desiredHz);
|
||||
Error computeStandardErrorCyclicExecutiveSleeping(const Stats& stats, double desiredHz);
|
||||
Time::Interval computeStandardSleepTime(const Stats& stats, double desiredHz);
|
||||
|
||||
private:
|
||||
State state;
|
||||
bool isRemoveRequested;
|
||||
Time timeofLastStep;
|
||||
Time timeofLastSleep;
|
||||
Time::Interval timespanOfLastStep;
|
||||
Error currentError;
|
||||
double priority;
|
||||
Time wakeTime;
|
||||
int overStepTimeThresholdCount;
|
||||
|
||||
boost::shared_ptr<CEvent> joinEvent; // Used when joining to the event after it is removed
|
||||
RunningAverageDutyCycle<> dutyCycle;
|
||||
RunningAverage<double> sleepRate;
|
||||
RunningAverage<double> runningAverageError;
|
||||
WindowAverageDutyCycle<> dutyCycleWindow;
|
||||
|
||||
void updateError(const Time& time);
|
||||
void notifyCoordinatorsPreStep();
|
||||
void preStep();
|
||||
void postStep(StepResult result);
|
||||
void notifyCoordinatorsPostStep();
|
||||
void updatePriority();
|
||||
void updateWakeTime();
|
||||
void startSleeping();
|
||||
void startWaiting();
|
||||
};
|
||||
} // namespace Aya
|
||||
404
engine/core/src/TaskScheduler.Thread.cpp
Normal file
404
engine/core/src/TaskScheduler.Thread.cpp
Normal file
@@ -0,0 +1,404 @@
|
||||
|
||||
#include "TaskScheduler.hpp"
|
||||
#include "boost.hpp"
|
||||
#include "TaskScheduler.Job.hpp"
|
||||
#include "CPUCount.hpp"
|
||||
#include "boost/enable_shared_from_this.hpp"
|
||||
#include "Log.hpp"
|
||||
#include "Profiler.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using boost::shared_ptr;
|
||||
|
||||
#include "CPUCount.hpp"
|
||||
|
||||
#define JOIN_TIMEOUT (1000 * 20) // 20 seconds
|
||||
|
||||
LOGGROUP(TaskSchedulerRun)
|
||||
LOGGROUP(TaskSchedulerFindJob)
|
||||
|
||||
class TaskScheduler::Thread : public boost::enable_shared_from_this<Thread>
|
||||
{
|
||||
boost::scoped_ptr<boost::thread> thread;
|
||||
volatile bool done;
|
||||
TaskScheduler* const taskScheduler;
|
||||
|
||||
Thread(TaskScheduler* taskScheduler)
|
||||
: done(false)
|
||||
, enabled(true)
|
||||
, taskScheduler(taskScheduler)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
shared_ptr<Job> job;
|
||||
volatile bool enabled;
|
||||
static shared_ptr<Thread> create(TaskScheduler* taskScheduler)
|
||||
{
|
||||
shared_ptr<Thread> thread(new Thread(taskScheduler));
|
||||
|
||||
static Aya::atomic<int> count;
|
||||
std::string name = Aya::format("Roblox TaskScheduler Thread %d", static_cast<int>(++count));
|
||||
|
||||
// loop holds a shared_ptr to the thread, so it won't be collected before the loop exits :)
|
||||
thread->thread.reset(new boost::thread(Aya::thread_wrapper(boost::bind(&Thread::loop, thread), name.c_str())));
|
||||
return thread;
|
||||
}
|
||||
|
||||
void end()
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
void join()
|
||||
{
|
||||
end();
|
||||
if (thread->get_id() != boost::this_thread::get_id())
|
||||
thread->timed_join(boost::posix_time::milliseconds(JOIN_TIMEOUT));
|
||||
}
|
||||
|
||||
void printJobInfo()
|
||||
{
|
||||
if (Aya::Log::current() && job)
|
||||
{
|
||||
Time now = Time::now<Time::Fast>();
|
||||
std::stringstream ss;
|
||||
if (job->isRunning())
|
||||
ss << "TaskScheduler::Job: " << job->name.c_str() << ", state: " << (int)job->getState()
|
||||
<< ", seconds spend in job: " << (now - job->stepStartTime).seconds();
|
||||
else
|
||||
ss << "TaskScheduler::Job: " << job->name.c_str() << ", state: " << (int)job->getState();
|
||||
|
||||
Aya::Log::current()->writeEntry(Aya::Log::Information, ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
~Thread()
|
||||
{
|
||||
join();
|
||||
}
|
||||
|
||||
void loop();
|
||||
void releaseJob();
|
||||
TaskScheduler::StepResult runJob();
|
||||
};
|
||||
|
||||
void TaskScheduler::printJobs()
|
||||
{
|
||||
std::for_each(threads.begin(), threads.end(), boost::bind(&Thread::printJobInfo, _1));
|
||||
}
|
||||
|
||||
void TaskScheduler::endAllThreads()
|
||||
{
|
||||
std::for_each(threads.begin(), threads.end(), boost::bind(&Thread::join, _1));
|
||||
}
|
||||
|
||||
void TaskScheduler::setThreadCount(ThreadPoolConfig threadConfig)
|
||||
{
|
||||
static const unsigned int kDefaultCoreCount = 1;
|
||||
int realCoreCount = RbxTotalUsableCoreCount(kDefaultCoreCount);
|
||||
|
||||
int requestedCount;
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
if (threadConfig == Auto)
|
||||
{
|
||||
// automatic: 1 thread per core
|
||||
requestedCount = realCoreCount;
|
||||
}
|
||||
else if (threadConfig >= PerCore1)
|
||||
{
|
||||
requestedCount = realCoreCount * (threadConfig - PerCore1 + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestedCount = (int)threadConfig;
|
||||
}
|
||||
|
||||
AYAASSERT(requestedCount > 0);
|
||||
|
||||
desiredThreadCount = (size_t)requestedCount;
|
||||
|
||||
while (threads.size() < (size_t)requestedCount)
|
||||
threads.push_back(Thread::create(this));
|
||||
}
|
||||
|
||||
|
||||
TaskScheduler::StepResult TaskScheduler::Thread::runJob()
|
||||
{
|
||||
job->stepStartTime = Time::now<Time::Fast>();
|
||||
|
||||
TaskScheduler::Job::Stats stats(*job, job->stepStartTime);
|
||||
|
||||
job->preStep();
|
||||
|
||||
TaskScheduler::StepResult result;
|
||||
++taskScheduler->runningJobCount;
|
||||
AYAASSERT(currentJob.get() == NULL);
|
||||
currentJob.reset(job.get());
|
||||
|
||||
// No need for exception handling. If an exception is thrown here
|
||||
// then we should abort the application.
|
||||
taskScheduler->taskCount++;
|
||||
result = job->step(stats);
|
||||
|
||||
AYAASSERT(currentJob.get() == job.get());
|
||||
currentJob.reset(NULL);
|
||||
--taskScheduler->runningJobCount;
|
||||
|
||||
job->postStep(result);
|
||||
|
||||
job->lastThreadUsed = shared_from_this();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool TaskScheduler::conflictsWithScheduledJob(Aya::TaskScheduler::Job* job) const
|
||||
{
|
||||
if (threads.size() == 1)
|
||||
return false;
|
||||
|
||||
const shared_ptr<Arbiter>& arbiter = job->getArbiter();
|
||||
if (!arbiter)
|
||||
return false;
|
||||
|
||||
for (Threads::const_iterator iter = threads.begin(); iter != threads.end(); ++iter)
|
||||
{
|
||||
Aya::TaskScheduler::Job* other = (*iter)->job.get();
|
||||
if (other)
|
||||
{
|
||||
if (Job::haveDifferentArbiters(job, other))
|
||||
continue; // different Arbiter domains can run concurrently
|
||||
|
||||
if (arbiter->areExclusive(job, other))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::Thread::releaseJob()
|
||||
{
|
||||
job->state = Job::Unknown;
|
||||
|
||||
if (job->isRemoveRequested)
|
||||
{
|
||||
if (job->joinEvent)
|
||||
job->joinEvent->Set();
|
||||
|
||||
if (taskScheduler->cyclicExecutiveEnabled && job->cyclicExecutive)
|
||||
{
|
||||
taskScheduler->releaseCyclicExecutive(job.get());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
taskScheduler->scheduleJob(*job);
|
||||
}
|
||||
job.reset();
|
||||
}
|
||||
|
||||
|
||||
bool TaskScheduler::shouldDropThread() const
|
||||
{
|
||||
AYAASSERT(desiredThreadCount <= threads.size());
|
||||
return desiredThreadCount < threads.size();
|
||||
}
|
||||
void TaskScheduler::dropThread(Thread* thread)
|
||||
{
|
||||
thread->end();
|
||||
for (Threads::iterator iter = threads.begin(); iter != threads.end(); ++iter)
|
||||
{
|
||||
if (iter->get() == thread)
|
||||
{
|
||||
threads.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskScheduler::disableThreads(int count, Threads& threads)
|
||||
{
|
||||
for (Threads::const_iterator iter = this->threads.begin(); count > 0 && iter != this->threads.end(); ++iter)
|
||||
{
|
||||
const shared_ptr<Thread>& t = *iter;
|
||||
if (t->job)
|
||||
continue;
|
||||
if (!t->enabled)
|
||||
continue;
|
||||
t->enabled = false;
|
||||
threads.push_back(t);
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskScheduler::enableThreads(Threads& threads)
|
||||
{
|
||||
for (Threads::const_iterator iter = threads.begin(); iter != threads.end(); ++iter)
|
||||
{
|
||||
AYAASSERT(!(*iter)->enabled);
|
||||
(*iter)->enabled = true;
|
||||
}
|
||||
threads.clear();
|
||||
}
|
||||
|
||||
void TaskScheduler::Thread::loop()
|
||||
{
|
||||
Profiler::onThreadCreate(format("TS %p", this).c_str());
|
||||
|
||||
taskScheduler->incrementThreadCount();
|
||||
|
||||
TaskScheduler::Threads participatingThreads; // threads that are turned off during a parallel run
|
||||
|
||||
shared_ptr<Thread> self(shared_from_this());
|
||||
while (!done)
|
||||
{
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(taskScheduler->mutex);
|
||||
FASTLOG1(FLog::TaskSchedulerFindJob, "Took mutex %p in thread TaskScheduler::Thread::loop", &(taskScheduler->mutex));
|
||||
|
||||
const Aya::Time start(taskScheduler->schedulerDutyCycle.startSample());
|
||||
|
||||
if (job)
|
||||
{
|
||||
taskScheduler->enableThreads(participatingThreads);
|
||||
job->allotedConcurrency = -1;
|
||||
job->notifyCoordinatorsPostStep();
|
||||
releaseJob();
|
||||
}
|
||||
|
||||
if (taskScheduler->shouldDropThread())
|
||||
taskScheduler->dropThread(this);
|
||||
else
|
||||
{
|
||||
if (enabled && !done)
|
||||
{
|
||||
job = taskScheduler->findJobToRun(self);
|
||||
if (job)
|
||||
{
|
||||
AYAASSERT(!job->isDisabled());
|
||||
// This must be synchronized with findJobToRun
|
||||
// because Coordinators expect atomicity with
|
||||
// isInhibited
|
||||
job->notifyCoordinatorsPreStep();
|
||||
|
||||
taskScheduler->disableThreads(job->getDesiredConcurrencyCount() - 1, participatingThreads);
|
||||
job->allotedConcurrency = int(participatingThreads.size()) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
taskScheduler->schedulerDutyCycle.stopSample(start);
|
||||
|
||||
FASTLOG1(FLog::TaskSchedulerFindJob, "Releasing mutex %p in TaskScheduler::Thread::loop", &(taskScheduler->mutex));
|
||||
|
||||
if (done)
|
||||
break;
|
||||
}
|
||||
|
||||
if (job)
|
||||
{
|
||||
if (runJob() == TaskScheduler::Done)
|
||||
job->isRemoveRequested = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Time::Interval sleepTime = taskScheduler->getShortestSleepTime();
|
||||
if (sleepTime.seconds() > 0)
|
||||
{
|
||||
// The most efficient thing is to sleep for a super-short period of time.
|
||||
// This is more efficient than waiting on a mutex, and the timespan is
|
||||
// short enough to make the system responsive.
|
||||
#ifdef _WIN32
|
||||
::Sleep(1);
|
||||
#else
|
||||
::usleep(1000);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(taskScheduler->mutex);
|
||||
taskScheduler->enableThreads(participatingThreads);
|
||||
job->allotedConcurrency = -1;
|
||||
job->notifyCoordinatorsPostStep();
|
||||
releaseJob();
|
||||
}
|
||||
|
||||
taskScheduler->decrementThreadCount();
|
||||
|
||||
Profiler::onThreadExit();
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::getJobsInfo(std::vector<shared_ptr<const Job>>& result)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
for (AllJobs::iterator iter = allJobs.begin(); iter != allJobs.end(); ++iter)
|
||||
result.push_back(*iter);
|
||||
}
|
||||
|
||||
static void setJobExtendedStatsWindow(shared_ptr<TaskScheduler::Job> job, double seconds)
|
||||
{
|
||||
job->getDutyCycleWindow().setMaxWindow(Time::Interval(seconds));
|
||||
if (seconds == 0.0)
|
||||
{
|
||||
job->getDutyCycleWindow().clear();
|
||||
}
|
||||
}
|
||||
|
||||
Aya::Time::Interval maxDutyCycleWindow(0.0);
|
||||
|
||||
void TaskScheduler::setJobsExtendedStatsWindow(double seconds)
|
||||
{
|
||||
maxDutyCycleWindow = Time::Interval(seconds);
|
||||
|
||||
std::vector<boost::shared_ptr<TaskScheduler::Job>> jobs;
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
for (AllJobs::iterator iter = allJobs.begin(); iter != allJobs.end(); ++iter)
|
||||
jobs.push_back(*iter);
|
||||
}
|
||||
std::for_each(jobs.begin(), jobs.end(), boost::bind(setJobExtendedStatsWindow, _1, seconds));
|
||||
}
|
||||
|
||||
void TaskScheduler::cancelCyclicExecutive()
|
||||
{
|
||||
// It turns out that determining this is a server may happen late enough that a lock is needed.
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
|
||||
cyclicExecutiveEnabled = false;
|
||||
for (CyclicExecutiveJobs::iterator i = cyclicExecutiveJobs.begin(); i != cyclicExecutiveJobs.end(); ++i)
|
||||
{
|
||||
Job* j = i->job.get();
|
||||
j->cyclicExecutive = false;
|
||||
if (i->isRunning == false)
|
||||
{
|
||||
scheduleJob(*j);
|
||||
}
|
||||
}
|
||||
cyclicExecutiveJobs.clear();
|
||||
|
||||
AYAASSERT(cyclicExecutiveJobs.empty());
|
||||
}
|
||||
|
||||
void TaskScheduler::releaseCyclicExecutive(TaskScheduler::Job* job)
|
||||
{
|
||||
AYAASSERT(std::find(cyclicExecutiveJobs.begin(), cyclicExecutiveJobs.end(), *job) != cyclicExecutiveJobs.end());
|
||||
cyclicExecutiveJobs.erase(std::find(cyclicExecutiveJobs.begin(), cyclicExecutiveJobs.end(), *job));
|
||||
}
|
||||
|
||||
void TaskScheduler::getJobsByName(const std::string& name, std::vector<boost::shared_ptr<const Job>>& result)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
for (AllJobs::iterator iter = allJobs.begin(); iter != allJobs.end(); ++iter)
|
||||
{
|
||||
if ((*iter)->name == name)
|
||||
{
|
||||
result.push_back(*iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
875
engine/core/src/TaskScheduler.cpp
Normal file
875
engine/core/src/TaskScheduler.cpp
Normal file
@@ -0,0 +1,875 @@
|
||||
// TaskScheduler.cpp : Defines the entry point for the console application.
|
||||
//
|
||||
|
||||
#include "TaskScheduler.hpp"
|
||||
#include "TaskScheduler.Job.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include "FastLog.hpp"
|
||||
#include "boost.hpp"
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include "ProcessPerfCounter.hpp"
|
||||
#include "Profiler.hpp"
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
|
||||
using namespace Aya;
|
||||
using boost::shared_ptr;
|
||||
|
||||
// #define TASKSCHEDULAR_PROFILING
|
||||
|
||||
LOGVARIABLE(TaskSchedulerTiming, 0)
|
||||
|
||||
FASTFLAGVARIABLE(TaskSchedulerCyclicExecutive, true)
|
||||
FASTFLAGVARIABLE(DebugTaskSchedulerProfiling, false)
|
||||
DYNAMIC_FASTINTVARIABLE(TaskSchedularBatchErrorCalcFPS, 300)
|
||||
DYNAMIC_FASTFLAGVARIABLE(CyclicExecutiveForServerTweaks, false)
|
||||
|
||||
Aya::TaskScheduler::PriorityMethod TaskScheduler::priorityMethod = AccumulatedError;
|
||||
// Aya::TaskScheduler::PriorityMethod TaskScheduler::priorityMethod = FIFO;
|
||||
|
||||
#ifdef AYA_TEST_BUILD
|
||||
int TaskScheduler::findJobFPS = 100;
|
||||
bool TaskScheduler::updateJobPriorityOnWake = false;
|
||||
#endif
|
||||
|
||||
double TaskScheduler::getSchedulerDutyCyclePerThread() const
|
||||
{
|
||||
return threads.size() ? schedulerDutyCycle.dutyCycle() / threads.size() : 0;
|
||||
}
|
||||
|
||||
Aya::thread_specific_reference<TaskScheduler::Job> TaskScheduler::currentJob;
|
||||
|
||||
struct PrintTaskSchedulerItem
|
||||
{
|
||||
int count;
|
||||
double averageError;
|
||||
double averagePriority;
|
||||
double dutyCycle;
|
||||
double averageStepsPerSecond;
|
||||
double coefficient_of_variation;
|
||||
double averageStepTime;
|
||||
PrintTaskSchedulerItem()
|
||||
: count(0)
|
||||
, averageError(0)
|
||||
, averagePriority(0)
|
||||
, dutyCycle(0)
|
||||
, averageStepsPerSecond(0)
|
||||
, coefficient_of_variation(0)
|
||||
, averageStepTime(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static std::string computeKey(const Aya::TaskScheduler::Job* job)
|
||||
{
|
||||
std::string key = job->name;
|
||||
size_t index = key.find(':');
|
||||
if (index == std::string::npos)
|
||||
return key;
|
||||
|
||||
index = key.find_last_of(' ');
|
||||
key = job->name.substr(0, index);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void PrintArbiters(std::vector<boost::shared_ptr<const Aya::TaskScheduler::Job>>& jobs)
|
||||
{
|
||||
std::set<shared_ptr<Aya::TaskScheduler::Arbiter>> arbiters;
|
||||
for (std::vector<boost::shared_ptr<const Aya::TaskScheduler::Job>>::iterator iter = jobs.begin(); iter != jobs.end(); ++iter)
|
||||
{
|
||||
if ((*iter)->getArbiter())
|
||||
arbiters.insert((*iter)->getArbiter());
|
||||
}
|
||||
|
||||
double total = 0;
|
||||
for (std::set<shared_ptr<Aya::TaskScheduler::Arbiter>>::iterator iter = arbiters.begin(); iter != arbiters.end(); ++iter)
|
||||
{
|
||||
double activity = (*iter)->getAverageActivity();
|
||||
printf("Arbiter %s\t%.1f%%\t%s\n", (*iter)->arbiterName().c_str(), 100.0 * activity, (*iter)->isThrottled() ? "throttled" : "");
|
||||
total += (*iter)->getAverageActivity();
|
||||
}
|
||||
printf("Total activity\t%.1f%%\n", 100.0 * total);
|
||||
}
|
||||
|
||||
void PrintTasks(std::vector<boost::shared_ptr<const Aya::TaskScheduler::Job>>& jobs)
|
||||
{
|
||||
printf("%25.25s\tSleep\tPrior\t%%\tSteps\tCV\tStep\n", "Arbiter:TaskName");
|
||||
for (std::vector<boost::shared_ptr<const Aya::TaskScheduler::Job>>::iterator iter = jobs.begin(); iter != jobs.end(); ++iter)
|
||||
{
|
||||
if ((*iter)->getSleepingTime() > Time::Interval(2))
|
||||
printf("%25.25s\tAsleep for %.1fs\n", (*iter)->getDebugName().c_str(), (*iter)->getSleepingTime().seconds());
|
||||
else
|
||||
printf("%25.25s\t%d%%\t%.1g\t%.1f%%\t%.1f/s\t%.1f%%\t%.3fs\n", (*iter)->getDebugName().c_str(), int(100.0 * (*iter)->averageSleepRate()),
|
||||
(*iter)->getPriority(), 100.0 * (*iter)->averageDutyCycle(), (*iter)->averageStepsPerSecond(),
|
||||
100.0 * (*iter)->getStepStats().stepInterval().coefficient_of_variation(), (*iter)->averageStepTime());
|
||||
}
|
||||
}
|
||||
|
||||
void PrintAggregatedTasks(std::vector<boost::shared_ptr<const Aya::TaskScheduler::Job>>& jobs)
|
||||
{
|
||||
std::map<std::string, PrintTaskSchedulerItem> items;
|
||||
for (std::vector<boost::shared_ptr<const Aya::TaskScheduler::Job>>::iterator iter = jobs.begin(); iter != jobs.end(); ++iter)
|
||||
{
|
||||
std::string key = computeKey(iter->get());
|
||||
if ((*iter)->getSleepingTime() < Time::Interval(5))
|
||||
{
|
||||
PrintTaskSchedulerItem& item(items[key]);
|
||||
item.count++;
|
||||
item.averageError += (*iter)->averageError();
|
||||
item.averagePriority += (*iter)->getPriority();
|
||||
item.dutyCycle += (*iter)->averageDutyCycle();
|
||||
item.averageStepsPerSecond += (*iter)->averageStepsPerSecond();
|
||||
item.coefficient_of_variation += (*iter)->getStepStats().stepInterval().coefficient_of_variation();
|
||||
item.averageStepTime += (*iter)->averageStepTime();
|
||||
}
|
||||
}
|
||||
|
||||
printf("%15.15s\tCount\tError\tPrior\t%%\tSteps\tCV\tStep\n", "Task");
|
||||
for (std::map<std::string, PrintTaskSchedulerItem>::iterator iter = items.begin(); iter != items.end(); ++iter)
|
||||
{
|
||||
PrintTaskSchedulerItem& item(iter->second);
|
||||
printf("%15.15s\t%d\t%.2f\t%.1f\t%.1f%%\t%.1f/s\t%.1f%%\t%.3fs\n", iter->first.c_str(), item.count, item.averageError / (double)item.count,
|
||||
item.averagePriority / (double)item.count, 100.0 * item.dutyCycle, item.averageStepsPerSecond / (double)item.count,
|
||||
100.0 * item.coefficient_of_variation / (double)item.count, item.averageStepTime / (double)item.count);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskScheduler::printDiagnostics(bool aggregateJobs)
|
||||
{
|
||||
std::vector<boost::shared_ptr<const Aya::TaskScheduler::Job>> jobs;
|
||||
getJobsInfo(jobs);
|
||||
|
||||
if (aggregateJobs)
|
||||
PrintAggregatedTasks(jobs);
|
||||
else
|
||||
PrintTasks(jobs);
|
||||
|
||||
PrintArbiters(jobs);
|
||||
|
||||
printf("sleep %.1f, wait %.1f, run %.2f, affinity %.2f, scheduling %.1f/s (%.2g%%)\n", numSleepingJobs(), numWaitingJobs(), numRunningJobs(),
|
||||
threadAffinity(), schedulerRate(), getSchedulerDutyCyclePerThread() * 100);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
Aya::ExclusiveArbiter Aya::ExclusiveArbiter::singleton;
|
||||
|
||||
bool Aya::ExclusiveArbiter::areExclusive(TaskScheduler::Job* task1, TaskScheduler::Job* task2)
|
||||
{
|
||||
AYAASSERT(task1->hasArbiter(this));
|
||||
AYAASSERT(task2->hasArbiter(this));
|
||||
return true;
|
||||
}
|
||||
|
||||
static TaskScheduler* sing;
|
||||
void TaskScheduler::static_init()
|
||||
{
|
||||
static TaskScheduler s;
|
||||
s.setThreadCount(TaskScheduler::Auto); // Use auto by default
|
||||
sing = &s;
|
||||
}
|
||||
TaskScheduler& TaskScheduler::singleton()
|
||||
{
|
||||
static boost::once_flag flag = BOOST_ONCE_INIT;
|
||||
boost::call_once(static_init, flag);
|
||||
return *sing;
|
||||
}
|
||||
|
||||
const static double lerpTaskScheduler = 0.05;
|
||||
|
||||
TaskScheduler::TaskScheduler()
|
||||
: sampleRunningJobCountEvent(true)
|
||||
, threadCount(0)
|
||||
, threadAffinityPreference(1.5) // TODO: Come up with a good number here
|
||||
, runningJobCount(0)
|
||||
, waitingJobCount(lerpTaskScheduler)
|
||||
, sleepingJobCount(lerpTaskScheduler)
|
||||
, averageRunningJobCount(lerpTaskScheduler)
|
||||
, schedulerDutyCycle(lerpTaskScheduler)
|
||||
, averageThreadAffinity(lerpTaskScheduler, 1)
|
||||
, nextWakeTime(Time::max())
|
||||
, lastSortTime(Time())
|
||||
, desiredThreadCount(0)
|
||||
, cyclicExecutiveLoopId(0)
|
||||
, DataModel30fpsThrottle(true)
|
||||
, cyclicExecutiveWaitForNextFrame(false)
|
||||
, nonCyclicJobsToDo(0)
|
||||
, lastCyclcTimestamp(Time::now<Time::Fast>())
|
||||
{
|
||||
runningJobCounterThread.reset(
|
||||
new boost::thread(Aya::thread_wrapper(boost::bind(&TaskScheduler::sampleRunningJobCount, this), "Roblox sampleRunningJobCount")));
|
||||
|
||||
// Publish the fast flag out to the schedulers API so that it can be overridden by specific clients.
|
||||
// E.g. this is not yet something to be done on the server.
|
||||
cyclicExecutiveEnabled = FFlag::TaskSchedulerCyclicExecutive;
|
||||
}
|
||||
|
||||
TaskScheduler::~TaskScheduler()
|
||||
{
|
||||
FASTLOG(FLog::TaskSchedulerInit, "Destroying TaskScheduler");
|
||||
sampleRunningJobCountEvent.Set();
|
||||
runningJobCounterThread->join();
|
||||
|
||||
endAllThreads();
|
||||
}
|
||||
|
||||
void TaskScheduler::remove(boost::shared_ptr<TaskScheduler::Job> task, bool joinTask, boost::function<void()> callbackPing)
|
||||
{
|
||||
if (!task)
|
||||
return;
|
||||
|
||||
// You can't join to yourself, but logically
|
||||
// there should be no concurrency risk to not join
|
||||
joinTask &= task.get() != currentJob.get();
|
||||
|
||||
shared_ptr<CEvent> joinEvent(joinTask ? new CEvent(true) : NULL);
|
||||
|
||||
remove(task, joinEvent);
|
||||
|
||||
if (joinTask)
|
||||
{
|
||||
if (callbackPing)
|
||||
{
|
||||
for (int i = 0; i < 10 * 60 * 5; ++i)
|
||||
{
|
||||
callbackPing();
|
||||
if (joinEvent->Wait(100))
|
||||
return;
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
AYAASSERT(false); // Why did it take so long to join?
|
||||
#endif
|
||||
AYACRASH(); // We want to learn about this. Blocking a long time means a thread in the pool is locked up!
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!joinEvent->Wait(1000 * 60 * 5))
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
AYAASSERT(false); // Why did it take so long to join?
|
||||
#endif
|
||||
AYACRASH(); // We want to learn about this. Blocking a long time means a thread in the pool is locked up!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TaskScheduler::reschedule(boost::shared_ptr<Job> job)
|
||||
{
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
if (job->SleepingHook::is_linked())
|
||||
{
|
||||
AYAASSERT(!cyclicExecutiveEnabled || job->cyclicExecutive == false);
|
||||
|
||||
sleepingJobs.erase(sleepingJobs.iterator_to(*job));
|
||||
scheduleJob(*job);
|
||||
}
|
||||
else if (cyclicExecutiveEnabled && job->cyclicExecutive)
|
||||
{
|
||||
CyclicExecutiveJobs::iterator i = std::find(cyclicExecutiveJobs.begin(), cyclicExecutiveJobs.end(), job);
|
||||
AYAASSERT(i != cyclicExecutiveJobs.end());
|
||||
if (i != cyclicExecutiveJobs.end())
|
||||
{
|
||||
// Reschedule immediately without waiting for other jobs. Prevents theoretical deadlocks.
|
||||
i->cyclicExecutiveExecuted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskScheduler::jobCompare(const CyclicExecutiveJob& jobA, const CyclicExecutiveJob& jobB)
|
||||
{
|
||||
return (jobA.job->cyclicPriority < jobB.job->cyclicPriority);
|
||||
}
|
||||
|
||||
void TaskScheduler::add(boost::shared_ptr<Job> job)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
AYAASSERT(allJobs.find(job) == allJobs.end());
|
||||
allJobs.insert(job);
|
||||
|
||||
if (cyclicExecutiveEnabled)
|
||||
{
|
||||
if (job->cyclicExecutive)
|
||||
{
|
||||
AYAASSERT(std::find(cyclicExecutiveJobs.begin(), cyclicExecutiveJobs.end(), job) == cyclicExecutiveJobs.end());
|
||||
cyclicExecutiveJobs.push_back(job);
|
||||
std::sort(cyclicExecutiveJobs.begin(), cyclicExecutiveJobs.end(), jobCompare);
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduleJob(*job);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
job->cyclicExecutive = false;
|
||||
scheduleJob(*job);
|
||||
}
|
||||
}
|
||||
|
||||
template<class List, class Item, class IsLess>
|
||||
void insert_from_back(List& list, Item& f, IsLess isLess)
|
||||
{
|
||||
for (typename List::reverse_iterator iter = list.rbegin(); iter != list.rend(); ++iter)
|
||||
if (!isLess(f, *iter))
|
||||
{
|
||||
list.insert(iter.base(), f);
|
||||
return;
|
||||
}
|
||||
list.push_front(f);
|
||||
}
|
||||
|
||||
void TaskScheduler::scheduleJob(Job& job)
|
||||
{
|
||||
if (cyclicExecutiveEnabled && job.cyclicExecutive)
|
||||
{
|
||||
CyclicExecutiveJobs::iterator i = std::find(cyclicExecutiveJobs.begin(), cyclicExecutiveJobs.end(), job);
|
||||
AYAASSERT(i != cyclicExecutiveJobs.end());
|
||||
if (i != cyclicExecutiveJobs.end())
|
||||
{
|
||||
i->isRunning = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
job.updateWakeTime();
|
||||
const Time now = Time::now<Time::Fast>();
|
||||
|
||||
if (job.wakeTime <= now)
|
||||
{
|
||||
#ifdef AYA_TEST_BUILD
|
||||
errorCalculationPerSec.sample();
|
||||
#endif
|
||||
job.updateError(now);
|
||||
if (job.currentError.error > 0)
|
||||
{
|
||||
job.sleepRate.sample(0);
|
||||
job.startWaiting();
|
||||
|
||||
#ifdef AYA_TEST_BUILD
|
||||
if (updateJobPriorityOnWake)
|
||||
#endif
|
||||
{
|
||||
job.updatePriority();
|
||||
}
|
||||
enqueueWaitingJob(job);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
job.sleepRate.sample(1);
|
||||
job.startSleeping();
|
||||
insert_from_back(sleepingJobs, job, Job::isLowerWakeTime);
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::remove(const boost::shared_ptr<TaskScheduler::Job>& job, boost::shared_ptr<CEvent> joinEvent)
|
||||
{
|
||||
Aya::mutex::scoped_lock lock(mutex);
|
||||
AYAASSERT(allJobs.find(job) != allJobs.end());
|
||||
FASTLOG1(FLog::TaskSchedulerRun, "Removing job %p from allJobs (::remove)", job.get());
|
||||
allJobs.erase(job);
|
||||
|
||||
if (job->SleepingHook::is_linked())
|
||||
{
|
||||
AYAASSERT(!cyclicExecutiveEnabled || job->cyclicExecutive == false);
|
||||
FASTLOG1(FLog::TaskSchedulerRun, "Removing job %p from sleepingJobs (::remove)", job.get());
|
||||
sleepingJobs.erase(sleepingJobs.iterator_to(*job));
|
||||
if (joinEvent)
|
||||
joinEvent->Set();
|
||||
}
|
||||
else if (job->WaitingHook::is_linked())
|
||||
{
|
||||
AYAASSERT(!cyclicExecutiveEnabled || job->cyclicExecutive == false);
|
||||
FASTLOG1(FLog::TaskSchedulerRun, "Removing job %p from waitingJobs (::remove)", job.get());
|
||||
|
||||
waitingJobs.erase(waitingJobs.iterator_to(*job));
|
||||
|
||||
if (joinEvent)
|
||||
joinEvent->Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cyclicExecutiveEnabled && job->cyclicExecutive)
|
||||
{
|
||||
CyclicExecutiveJobs::iterator i = std::find(cyclicExecutiveJobs.begin(), cyclicExecutiveJobs.end(), job);
|
||||
AYAASSERT(i != cyclicExecutiveJobs.end());
|
||||
if (i != cyclicExecutiveJobs.end())
|
||||
{
|
||||
if (!i->isRunning)
|
||||
{
|
||||
cyclicExecutiveJobs.erase(i);
|
||||
if (joinEvent)
|
||||
joinEvent->Set();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (joinEvent)
|
||||
{
|
||||
AYAASSERT(!job->joinEvent); // Did somebody else already try to remove this task????
|
||||
// TODO: Support multiple joins by using a collection?
|
||||
|
||||
// We must wait for the task to be completed before signaling the joinEvent
|
||||
job->joinEvent = joinEvent;
|
||||
}
|
||||
|
||||
// We can't remove it yet. Wait for it to finish stepping
|
||||
job->isRemoveRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskScheduler::incrementThreadCount()
|
||||
{
|
||||
++threadCount;
|
||||
}
|
||||
void TaskScheduler::decrementThreadCount()
|
||||
{
|
||||
--threadCount;
|
||||
}
|
||||
|
||||
bool TaskScheduler::areExclusive(TaskScheduler::Job* task1, TaskScheduler::Job* task2, const shared_ptr<Arbiter>& arbiterHint)
|
||||
{
|
||||
if (Job::haveDifferentArbiters(task1, task2))
|
||||
return false; // different Arbiter domains can run concurrently
|
||||
|
||||
if (!arbiterHint)
|
||||
return false; // No Arbiter means the task can run concurrently
|
||||
|
||||
return arbiterHint->areExclusive(task1, task2);
|
||||
}
|
||||
|
||||
void TaskScheduler::sampleRunningJobCount()
|
||||
{
|
||||
while (!sampleRunningJobCountEvent.Wait(71))
|
||||
{
|
||||
waitingJobCount.sample(int(waitingJobs.size() + cyclicExecutiveJobs.size()));
|
||||
sleepingJobCount.sample(int(sleepingJobs.size()));
|
||||
averageRunningJobCount.sample(int(runningJobCount));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::enqueueWaitingJob(Job& job)
|
||||
{
|
||||
FASTLOG1(FLog::TaskSchedulerFindJob, "Adding job %p to waitingJobs (::enqueueWaitingJob)", &job);
|
||||
|
||||
AYAASSERT(!cyclicExecutiveEnabled || job.cyclicExecutive == false);
|
||||
|
||||
waitingJobs.push_back(job);
|
||||
}
|
||||
|
||||
|
||||
Time::Interval TaskScheduler::getShortestSleepTime() const
|
||||
{
|
||||
return nextWakeTime - Time::now<Time::Fast>();
|
||||
}
|
||||
|
||||
|
||||
void TaskScheduler::wakeSleepingJobs()
|
||||
{
|
||||
Time now = Time::now<Time::Fast>();
|
||||
|
||||
SleepingJobs::iterator iter = sleepingJobs.begin();
|
||||
while (iter != sleepingJobs.end())
|
||||
{
|
||||
Job& job(*iter);
|
||||
AYAASSERT(!cyclicExecutiveEnabled || job.cyclicExecutive == false);
|
||||
|
||||
if (job.wakeTime > now)
|
||||
{
|
||||
nextWakeTime = job.wakeTime;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
iter = sleepingJobs.erase(iter);
|
||||
#ifdef AYA_TEST_BUILD
|
||||
errorCalculationPerSec.sample();
|
||||
#endif
|
||||
job.updateError(now);
|
||||
|
||||
#ifdef AYA_TEST_BUILD
|
||||
if (updateJobPriorityOnWake)
|
||||
#endif
|
||||
{
|
||||
job.updatePriority();
|
||||
}
|
||||
enqueueWaitingJob(job);
|
||||
}
|
||||
}
|
||||
nextWakeTime = Time::max();
|
||||
}
|
||||
|
||||
void TaskScheduler::checkStillWaitingNextFrame(Time now)
|
||||
{
|
||||
float fpsCap = 60.0f;
|
||||
|
||||
switch (Aya::GameBasicSettings::singleton().getMaxFramerate())
|
||||
{
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_30:
|
||||
fpsCap = 30.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_60:
|
||||
fpsCap = 60.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_75:
|
||||
fpsCap = 75.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_120:
|
||||
fpsCap = 120.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_144:
|
||||
fpsCap = 144.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_200:
|
||||
fpsCap = 200.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_240:
|
||||
fpsCap = 240.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_360:
|
||||
fpsCap = 360.0f;
|
||||
break;
|
||||
case Aya::GameBasicSettings::MaxFramerate::FPS_UNCAPPED:
|
||||
fpsCap = 9999.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
float minTimespan = 1.0 / fpsCap;
|
||||
if ((now - lastCyclcTimestamp).seconds() < minTimespan)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
lastCyclcTimestamp = now;
|
||||
cyclicExecutiveWaitForNextFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
boost::shared_ptr<TaskScheduler::Job> TaskScheduler::findJobToRun(shared_ptr<Thread> requestingThread)
|
||||
{
|
||||
#ifdef TASKSCHEDULAR_PROFILING
|
||||
int theCount = 0;
|
||||
Aya::Timer<Aya::Time::Precise> timer;
|
||||
#endif
|
||||
Time now = Time::now<Time::Fast>();
|
||||
|
||||
if (cyclicExecutiveEnabled)
|
||||
{
|
||||
// Once all "cyclic jobs" have been done, this code falls through to the low priority queue one time.
|
||||
bool hasRemaingJobs = false;
|
||||
|
||||
if ((nonCyclicJobsToDo == 0) && cyclicExecutiveWaitForNextFrame && DataModel30fpsThrottle)
|
||||
{
|
||||
checkStillWaitingNextFrame(now);
|
||||
}
|
||||
|
||||
if (!cyclicExecutiveWaitForNextFrame || !DataModel30fpsThrottle)
|
||||
{
|
||||
for (CyclicExecutiveJobs::iterator iter = cyclicExecutiveJobs.begin(); iter != cyclicExecutiveJobs.end(); ++iter)
|
||||
{
|
||||
Job& job(*iter->job);
|
||||
if (iter->cyclicExecutiveExecuted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (job.isDisabled())
|
||||
continue;
|
||||
|
||||
hasRemaingJobs = true;
|
||||
|
||||
if (iter->isRunning)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (conflictsWithScheduledJob(&job))
|
||||
continue;
|
||||
|
||||
if (job.tryJobAgain())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
iter->cyclicExecutiveExecuted = true;
|
||||
|
||||
job.updateError(now);
|
||||
if (job.currentError.isDefault())
|
||||
{
|
||||
// Various jobs, including all the rendering tasks, signal they do not want
|
||||
// to run by setting their error to 0.
|
||||
continue;
|
||||
}
|
||||
|
||||
iter->isRunning = true;
|
||||
|
||||
shared_ptr<Job> result = job.shared_from_this();
|
||||
FASTLOG2(FLog::TaskSchedulerFindJob, "CyclicExecutive, job: %p, arbiter %p", result.get(), result->getArbiter().get());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!hasRemaingJobs)
|
||||
{
|
||||
for (CyclicExecutiveJobs::iterator iter = cyclicExecutiveJobs.begin(); iter != cyclicExecutiveJobs.end(); ++iter)
|
||||
{
|
||||
iter->cyclicExecutiveExecuted = false;
|
||||
}
|
||||
cyclicExecutiveWaitForNextFrame = true;
|
||||
nonCyclicJobsToDo = numNonCyclicJobsWithWork();
|
||||
FASTLOG1F(FLog::TaskSchedulerTiming, "Finished all jobs in Cycle in: %4.7f",
|
||||
(float)(Time::now<Time::Fast>() - lastCyclcTimestamp).seconds());
|
||||
}
|
||||
}
|
||||
|
||||
if (nonCyclicJobsToDo > 0)
|
||||
{
|
||||
// Run nonCyclicExecutive jobs that have been in the Queue before moving on to
|
||||
// the next CyclicExecutive frame
|
||||
shared_ptr<Job> result = findJobToRunNonCyclicJobs(requestingThread, now);
|
||||
if (result)
|
||||
{
|
||||
// At end of Cyclic RenderJob will have DataModel Lock
|
||||
// We must make sure that we only decrement jobs to do when
|
||||
// we do them, because we are almost guaranteed that result
|
||||
// will be null the first few checks.
|
||||
nonCyclicJobsToDo--;
|
||||
}
|
||||
else if (numNonCyclicJobsWithWork() == 0)
|
||||
{
|
||||
// In case something modifies the jobs after end of cycle
|
||||
// we want to be able to verify that job's error didn't
|
||||
// return to 0
|
||||
nonCyclicJobsToDo = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If tryJobAgain breaks out of the loop, we want the opportunity to do
|
||||
// work on Non-Cyclic jobs while we wait for job to be ready.
|
||||
shared_ptr<Job> result = findJobToRunNonCyclicJobs(requestingThread, now);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else // !cyclicExecutiveEnabled
|
||||
{
|
||||
// nextScheduledJob is an optimization. Under heavy load, we cut the time for the scheduler
|
||||
// in half because it buffers a job from the last go-around.
|
||||
// TODO: Buffer n jobs instead?
|
||||
// TODO: Buffer one job per thread to help with thread affinity?
|
||||
if (nextScheduledJob)
|
||||
{
|
||||
shared_ptr<Job> result(nextScheduledJob);
|
||||
nextScheduledJob.reset();
|
||||
|
||||
if (result->WaitingHook::is_linked())
|
||||
if (!result->isDisabled())
|
||||
if (!conflictsWithScheduledJob(result.get()))
|
||||
{
|
||||
FASTLOG1(FLog::TaskSchedulerFindJob, "Removing nextScheduledJob %p from waitingJobs (::findJobToRun)", result.get());
|
||||
waitingJobs.erase(waitingJobs.iterator_to(*result));
|
||||
averageThreadAffinity.sample(result->lastThreadUsed.lock() == requestingThread);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return findJobToRunNonCyclicJobs(requestingThread, now);
|
||||
}
|
||||
}
|
||||
|
||||
int TaskScheduler::numNonCyclicJobsWithWork()
|
||||
{
|
||||
int jobCount = 0;
|
||||
for (WaitingJobs::iterator iter = waitingJobs.begin(); iter != waitingJobs.end(); ++iter)
|
||||
{
|
||||
Job& job(*iter);
|
||||
if (job.currentError.error > 0 && !job.isDisabled())
|
||||
{
|
||||
// We only add jobs that have work to do!
|
||||
jobCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return jobCount;
|
||||
}
|
||||
|
||||
shared_ptr<TaskScheduler::Job> TaskScheduler::findJobToRunNonCyclicJobs(boost::shared_ptr<Thread> requestingThread, Aya::Time now)
|
||||
{
|
||||
shared_ptr<TaskScheduler::Job> result;
|
||||
wakeSleepingJobs();
|
||||
|
||||
WaitingJobs::iterator bestJob = waitingJobs.end();
|
||||
|
||||
// Note: These definitions don't strictly need to be initialized, but it eliminates pesky compiler warnings
|
||||
bool bestHasAffinity = false;
|
||||
bool bestIsThrottled = false;
|
||||
|
||||
bool shouldCalcError;
|
||||
Time::Interval timeSinceLastSorting = now - lastSortTime;
|
||||
int fps = DFInt::TaskSchedularBatchErrorCalcFPS;
|
||||
if (fps < 10)
|
||||
{
|
||||
fps = 10;
|
||||
}
|
||||
double errorCalcInterval = 1.f / (double)fps;
|
||||
|
||||
#ifdef AYA_TEST_BUILD
|
||||
errorCalcInterval = 1.f / findJobFPS;
|
||||
#endif
|
||||
shouldCalcError = (timeSinceLastSorting.seconds() > errorCalcInterval);
|
||||
if (shouldCalcError)
|
||||
{
|
||||
#ifdef AYA_TEST_BUILD
|
||||
sortFrequency.sample();
|
||||
#endif
|
||||
lastSortTime = now;
|
||||
}
|
||||
|
||||
FASTLOG1(FLog::TaskSchedulerFindJob, "Starting to iterate through waitingJobs. Size = %d", waitingJobs.size());
|
||||
|
||||
for (WaitingJobs::iterator iter = waitingJobs.begin(); iter != waitingJobs.end(); ++iter)
|
||||
{
|
||||
#ifdef TASKSCHEDULAR_PROFILING
|
||||
theCount++;
|
||||
#endif
|
||||
Job& job(*iter);
|
||||
|
||||
if (job.isDisabled())
|
||||
continue;
|
||||
|
||||
if (priorityMethod != FIFO)
|
||||
{
|
||||
if (shouldCalcError)
|
||||
{
|
||||
#ifdef AYA_TEST_BUILD
|
||||
errorCalculationPerSec.sample();
|
||||
#endif
|
||||
job.updateError(now);
|
||||
}
|
||||
|
||||
if (job.currentError.error <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (conflictsWithScheduledJob(&job))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldCalcError)
|
||||
{
|
||||
job.updatePriority();
|
||||
}
|
||||
|
||||
const bool hasAffinity = job.lastThreadUsed.lock() == requestingThread;
|
||||
bool match = false;
|
||||
|
||||
shared_ptr<Arbiter> const arbiter(job.getArbiter());
|
||||
|
||||
if (bestJob == waitingJobs.end())
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
else if (job.currentError.urgent != bestJob->currentError.urgent)
|
||||
{
|
||||
if (job.currentError.urgent)
|
||||
match = true;
|
||||
}
|
||||
else if (bestIsThrottled && (!arbiter || !arbiter->isThrottled()))
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
else if (hasAffinity == bestHasAffinity)
|
||||
{
|
||||
if (job.priority > bestJob->priority)
|
||||
match = true;
|
||||
}
|
||||
else if (hasAffinity)
|
||||
{
|
||||
if (job.priority * threadAffinityPreference > bestJob->priority)
|
||||
match = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (job.priority > bestJob->priority * threadAffinityPreference)
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
if (!cyclicExecutiveEnabled)
|
||||
{
|
||||
// Cache off old best job
|
||||
if (bestJob != waitingJobs.end())
|
||||
{
|
||||
if (!areExclusive(&job, &*bestJob, arbiter))
|
||||
{
|
||||
nextScheduledJob = bestJob->shared_from_this();
|
||||
}
|
||||
else if (nextScheduledJob && areExclusive(&job, &*nextScheduledJob, arbiter))
|
||||
{
|
||||
nextScheduledJob.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bestJob = iter;
|
||||
bestHasAffinity = hasAffinity;
|
||||
|
||||
if (priorityMethod == FIFO)
|
||||
break;
|
||||
|
||||
bestIsThrottled = arbiter && arbiter->isThrottled();
|
||||
}
|
||||
}
|
||||
|
||||
FASTLOG(FLog::TaskSchedulerFindJob, "Finished iterating through waitingJobs");
|
||||
|
||||
if (bestJob != waitingJobs.end())
|
||||
{
|
||||
result = bestJob->shared_from_this();
|
||||
AYAASSERT(bestJob->WaitingHook::is_linked());
|
||||
waitingJobs.erase(waitingJobs.iterator_to(*bestJob));
|
||||
averageThreadAffinity.sample(bestHasAffinity);
|
||||
FASTLOG3(FLog::TaskSchedulerFindJob, "RunJob, job: %p, arbiter %p error: %u", result.get(), result->getArbiter().get(),
|
||||
(unsigned)result->currentError.error);
|
||||
}
|
||||
|
||||
|
||||
#ifdef TASKSCHEDULAR_PROFILING
|
||||
if (FFlag::DebugTaskSchedulerProfiling)
|
||||
{
|
||||
static Aya::Timer<Aya::Time::Fast> totalTimer;
|
||||
static int totalCount = 0;
|
||||
static double taskSchedulerTime = 0.f;
|
||||
static int lastTotalCount = 0;
|
||||
|
||||
double timeElapsed = timer.delta().seconds();
|
||||
taskSchedulerTime += timeElapsed;
|
||||
totalCount += theCount;
|
||||
if (totalCount - lastTotalCount >= 1000000)
|
||||
{
|
||||
lastTotalCount = totalCount;
|
||||
double totalTimeElapsed = totalTimer.delta().seconds();
|
||||
printf("Jobs: %d, time used: %f, total time: %f, job/s: %d, percent: %.2f%%\n", totalCount, taskSchedulerTime, totalTimeElapsed,
|
||||
(int)((double)totalCount / totalTimeElapsed), taskSchedulerTime / totalTimeElapsed * 100.f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
Aya::atomic<int> Aya::SimpleThrottlingArbiter::arbiterCount;
|
||||
bool Aya::SimpleThrottlingArbiter::isThrottlingEnabled = false;
|
||||
358
engine/core/src/TaskScheduler.hpp
Normal file
358
engine/core/src/TaskScheduler.hpp
Normal file
@@ -0,0 +1,358 @@
|
||||
#pragma once
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "RunningAverage.hpp"
|
||||
#include "Declarations.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include "threadsafe.hpp"
|
||||
#include "boost.hpp"
|
||||
#include "CEvent.hpp"
|
||||
#include "atomic.hpp"
|
||||
|
||||
#include "boost/thread.hpp"
|
||||
#include "boost/function.hpp"
|
||||
#include "boost/shared_ptr.hpp"
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include "boost/noncopyable.hpp"
|
||||
#include "boost/intrusive/list.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
|
||||
LOGGROUP(TaskSchedulerInit)
|
||||
LOGGROUP(TaskSchedulerRun)
|
||||
LOGGROUP(TaskSchedulerFindJob)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
/// A singleton object responsible for scheduling the execution TaskScheduler.Jobs.
|
||||
class TaskScheduler
|
||||
{
|
||||
struct SleepingTag;
|
||||
typedef boost::intrusive::list_base_hook<boost::intrusive::tag<SleepingTag>> SleepingHook;
|
||||
struct WaitingTag;
|
||||
typedef boost::intrusive::list_base_hook<boost::intrusive::tag<WaitingTag>> WaitingHook;
|
||||
|
||||
public:
|
||||
class Thread;
|
||||
typedef std::vector<boost::shared_ptr<Thread>> Threads;
|
||||
class Job;
|
||||
bool DataModel30fpsThrottle;
|
||||
Time lastCyclcTimestamp;
|
||||
bool cyclicExecutiveWaitForNextFrame;
|
||||
int nonCyclicJobsToDo;
|
||||
int cyclicExecutiveLoopId;
|
||||
class AyaBaseClass Arbiter
|
||||
{
|
||||
protected:
|
||||
ActivityMeter<2> activityMeter;
|
||||
|
||||
public:
|
||||
virtual std::string arbiterName() = 0;
|
||||
virtual bool areExclusive(Job* job1, Job* job2) = 0;
|
||||
virtual bool isThrottled() = 0;
|
||||
virtual void preStep(TaskScheduler::Job* job)
|
||||
{
|
||||
activityMeter.increment();
|
||||
}
|
||||
virtual void postStep(TaskScheduler::Job* job)
|
||||
{
|
||||
activityMeter.decrement();
|
||||
}
|
||||
double getAverageActivity()
|
||||
{
|
||||
return activityMeter.averageValue();
|
||||
}
|
||||
virtual Arbiter* getSyncronizationArbiter()
|
||||
{
|
||||
return this;
|
||||
};
|
||||
virtual int getNumPlayers() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
Done, // The job will be removed from the TaskScheduler
|
||||
Stepped, // Another step will be scheduled
|
||||
} StepResult;
|
||||
|
||||
static TaskScheduler& singleton();
|
||||
|
||||
typedef enum
|
||||
{
|
||||
LastError,
|
||||
AccumulatedError,
|
||||
FIFO
|
||||
} PriorityMethod;
|
||||
static PriorityMethod priorityMethod;
|
||||
|
||||
#ifdef AYA_TEST_BUILD
|
||||
static int findJobFPS;
|
||||
static bool updateJobPriorityOnWake;
|
||||
#endif
|
||||
double threadAffinityPreference;
|
||||
typedef enum
|
||||
{
|
||||
PerCore4 = 104,
|
||||
PerCore3 = 103,
|
||||
PerCore2 = 102,
|
||||
PerCore1 = 101,
|
||||
Auto = 0,
|
||||
Threads1 = 1,
|
||||
Threads2 = 2,
|
||||
Threads3 = 3,
|
||||
Threads4 = 4,
|
||||
Threads8 = 8,
|
||||
Threads16 = 16
|
||||
} ThreadPoolConfig;
|
||||
|
||||
bool shouldDropThread() const;
|
||||
void dropThread(Thread* thread);
|
||||
|
||||
size_t getThreadCount()
|
||||
{
|
||||
return threadCount;
|
||||
}
|
||||
void setThreadCount(ThreadPoolConfig threadConfig);
|
||||
void disableThreads(int count, Threads& threads);
|
||||
void enableThreads(Threads& threads);
|
||||
|
||||
void add(boost::shared_ptr<TaskScheduler::Job> job);
|
||||
void reschedule(boost::shared_ptr<TaskScheduler::Job> job);
|
||||
|
||||
void remove(boost::shared_ptr<TaskScheduler::Job> job)
|
||||
{
|
||||
remove(job, false, NULL);
|
||||
}
|
||||
// This version of remove might lead to deadlocks in some cases, so try not to use it
|
||||
void removeBlocking(boost::shared_ptr<TaskScheduler::Job> job)
|
||||
{
|
||||
remove(job, true, NULL);
|
||||
}
|
||||
// This version of remove calls back several times a second while waiting.
|
||||
// You might use this to process events in order to avoid deadlocks.
|
||||
void removeBlocking(boost::shared_ptr<TaskScheduler::Job> job, boost::function<void()> callbackPing)
|
||||
{
|
||||
remove(job, true, callbackPing);
|
||||
}
|
||||
|
||||
void getJobsInfo(std::vector<boost::shared_ptr<const Job>>& result);
|
||||
void getJobsByName(const std::string& name, std::vector<boost::shared_ptr<const Job>>& result);
|
||||
|
||||
// Performance counters
|
||||
double numSleepingJobs() const
|
||||
{
|
||||
return sleepingJobCount.value();
|
||||
}
|
||||
double numWaitingJobs() const
|
||||
{
|
||||
return waitingJobCount.value();
|
||||
}
|
||||
double numRunningJobs() const
|
||||
{
|
||||
return averageRunningJobCount.value();
|
||||
}
|
||||
double threadAffinity() const
|
||||
{
|
||||
return averageThreadAffinity.value();
|
||||
}
|
||||
size_t threadPoolSize() const
|
||||
{
|
||||
return threads.size();
|
||||
}
|
||||
double schedulerRate() const
|
||||
{
|
||||
return schedulerDutyCycle.rate();
|
||||
}
|
||||
double getSchedulerDutyCyclePerThread() const;
|
||||
double getErrorCalculationRate() const
|
||||
{
|
||||
return errorCalculationPerSec.rate();
|
||||
}
|
||||
double getSortFrequency() const
|
||||
{
|
||||
return sortFrequency.rate();
|
||||
}
|
||||
Aya::atomic<int> taskCount;
|
||||
void printDiagnostics(bool aggregateJobs);
|
||||
void printJobs();
|
||||
void setJobsExtendedStatsWindow(double seconds); // set seconds to 0.0 to disable.
|
||||
void cancelCyclicExecutive();
|
||||
bool isCyclicExecutive()
|
||||
{
|
||||
return cyclicExecutiveEnabled;
|
||||
}
|
||||
void releaseCyclicExecutive(TaskScheduler::Job* job);
|
||||
|
||||
private:
|
||||
// ** Here for thread saftey. **
|
||||
struct CyclicExecutiveJob
|
||||
{
|
||||
boost::shared_ptr<TaskScheduler::Job> job;
|
||||
bool cyclicExecutiveExecuted;
|
||||
bool isRunning;
|
||||
|
||||
CyclicExecutiveJob(const boost::shared_ptr<TaskScheduler::Job>& j)
|
||||
{
|
||||
job = j;
|
||||
cyclicExecutiveExecuted = false;
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
// Allows for using std::find.
|
||||
bool operator==(const boost::shared_ptr<TaskScheduler::Job>& j)
|
||||
{
|
||||
return job == j;
|
||||
}
|
||||
bool operator==(const TaskScheduler::Job& j)
|
||||
{
|
||||
return job.get() == &j;
|
||||
}
|
||||
};
|
||||
|
||||
TaskScheduler();
|
||||
~TaskScheduler();
|
||||
void endAllThreads();
|
||||
void sampleRunningJobCount();
|
||||
|
||||
static bool jobCompare(const CyclicExecutiveJob& jobA, const CyclicExecutiveJob& jobB);
|
||||
|
||||
void remove(boost::shared_ptr<TaskScheduler::Job> job, bool joinJob, boost::function<void()> callbackPing);
|
||||
void remove(const boost::shared_ptr<TaskScheduler::Job>& job, boost::shared_ptr<CEvent> joinEvent);
|
||||
|
||||
void scheduleJob(Job& job);
|
||||
|
||||
static bool areExclusive(Job* job1, Job* job2, const shared_ptr<Arbiter>& arbiterHint);
|
||||
bool conflictsWithScheduledJob(Job* item) const;
|
||||
|
||||
void incrementThreadCount();
|
||||
void decrementThreadCount();
|
||||
|
||||
Aya::mutex mutex;
|
||||
|
||||
typedef std::set<shared_ptr<Job>> AllJobs;
|
||||
AllJobs allJobs;
|
||||
typedef boost::intrusive::list<Job, boost::intrusive::base_hook<SleepingHook>> SleepingJobs;
|
||||
SleepingJobs sleepingJobs;
|
||||
bool cyclicExecutiveEnabled;
|
||||
typedef std::vector<CyclicExecutiveJob> CyclicExecutiveJobs;
|
||||
CyclicExecutiveJobs cyclicExecutiveJobs;
|
||||
typedef boost::intrusive::list<Job, boost::intrusive::base_hook<WaitingHook>> WaitingJobs;
|
||||
WaitingJobs waitingJobs;
|
||||
|
||||
shared_ptr<Job> nextScheduledJob;
|
||||
|
||||
void wakeSleepingJobs();
|
||||
void enqueueWaitingJob(Job& job);
|
||||
Time::Interval getShortestSleepTime() const;
|
||||
boost::shared_ptr<Job> findJobToRun(boost::shared_ptr<Thread> requestingThread);
|
||||
boost::shared_ptr<Job> findJobToRunNonCyclicJobs(boost::shared_ptr<Thread> requestingThread, Aya::Time now);
|
||||
int numNonCyclicJobsWithWork();
|
||||
|
||||
void checkStillWaitingNextFrame(Time now);
|
||||
|
||||
RunningAverage<int> sleepingJobCount;
|
||||
RunningAverage<int> waitingJobCount;
|
||||
RunningAverage<int> averageRunningJobCount;
|
||||
RunningAverageDutyCycle<Time::Precise> schedulerDutyCycle; // time spent scheduling jobs
|
||||
RunningAverage<double> averageThreadAffinity;
|
||||
RunningAverageTimeInterval<> errorCalculationPerSec;
|
||||
RunningAverageTimeInterval<> sortFrequency;
|
||||
|
||||
Aya::atomic<int> runningJobCount;
|
||||
|
||||
Time nextWakeTime;
|
||||
|
||||
Time lastSortTime;
|
||||
|
||||
Threads threads;
|
||||
size_t desiredThreadCount;
|
||||
|
||||
CEvent sampleRunningJobCountEvent;
|
||||
boost::scoped_ptr<boost::thread> runningJobCounterThread;
|
||||
|
||||
Aya::atomic<int> threadCount;
|
||||
|
||||
static Aya::thread_specific_reference<TaskScheduler::Job> currentJob;
|
||||
static void static_init();
|
||||
};
|
||||
|
||||
|
||||
// A simple arbiter that prevents all members of it to execute concurrently
|
||||
class ExclusiveArbiter
|
||||
: public TaskScheduler::Arbiter
|
||||
, boost::noncopyable
|
||||
{
|
||||
public:
|
||||
virtual bool areExclusive(TaskScheduler::Job* job1, TaskScheduler::Job* job2);
|
||||
virtual std::string arbiterName()
|
||||
{
|
||||
return "ExclusiveArbiter";
|
||||
}
|
||||
virtual bool isThrottled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static ExclusiveArbiter singleton;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class SimpleThrottlingArbiter : public TaskScheduler::Arbiter
|
||||
{
|
||||
mutable bool throttled;
|
||||
Aya::atomic<int> updatingThrottle;
|
||||
static Aya::atomic<int> arbiterCount;
|
||||
|
||||
public:
|
||||
static bool isThrottlingEnabled;
|
||||
|
||||
SimpleThrottlingArbiter()
|
||||
: throttled(false)
|
||||
, updatingThrottle(0)
|
||||
{
|
||||
++arbiterCount;
|
||||
}
|
||||
|
||||
~SimpleThrottlingArbiter()
|
||||
{
|
||||
--arbiterCount;
|
||||
}
|
||||
|
||||
virtual bool isThrottled()
|
||||
{
|
||||
if (!isThrottlingEnabled)
|
||||
return false;
|
||||
|
||||
long count = arbiterCount;
|
||||
if (count <= 1)
|
||||
return false;
|
||||
if (updatingThrottle.swap(1) == 0)
|
||||
{
|
||||
double cutoff = ((double)Aya::TaskScheduler::singleton().getThreadCount()) / (double)count;
|
||||
// hysteresis
|
||||
if (throttled)
|
||||
{
|
||||
throttled = getAverageActivity() >= cutoff;
|
||||
}
|
||||
else
|
||||
{
|
||||
throttled = getAverageActivity() >= 1.1 * cutoff;
|
||||
}
|
||||
|
||||
--updatingThrottle;
|
||||
}
|
||||
|
||||
return throttled;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Aya
|
||||
65
engine/core/src/Thread.hpp
Normal file
65
engine/core/src/Thread.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// Returns a function that executes threadfunc with a given name
|
||||
boost::function0<void> thread_wrapper(const boost::function0<void>& threadfunc, const char* name);
|
||||
|
||||
void set_thread_name(const char* name);
|
||||
|
||||
// Returns the name of a thread if it was created with one of the above functions
|
||||
const char* get_thread_name();
|
||||
|
||||
// The worker thread runs a process in a low-priority thread
|
||||
// The function you provide is called:
|
||||
// 1) Once after worker_thread is constructed
|
||||
// 2) Immediately after the function returns work_result::more
|
||||
// 3) After wake() is called
|
||||
//
|
||||
// The thread ends execution after worker_thread is deleted, but it doesn't
|
||||
// interrupt processing of work_function
|
||||
//
|
||||
// The client is responsible for ensuring that any data used by the work_function
|
||||
// is still valid. Note that the work_function might continue to execute for a
|
||||
// period of time after the owning worker_thread is destroyed.
|
||||
//
|
||||
class worker_thread : public boost::noncopyable
|
||||
{
|
||||
struct data
|
||||
{
|
||||
boost::mutex sync;
|
||||
boost::condition_variable_any wakeCondition; // TODO: condition_variable?
|
||||
bool endRequest;
|
||||
|
||||
data()
|
||||
: endRequest(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
boost::shared_ptr<data> _data;
|
||||
boost::thread t;
|
||||
|
||||
public:
|
||||
enum work_result
|
||||
{
|
||||
done,
|
||||
more
|
||||
};
|
||||
worker_thread(const boost::function0<work_result>& work_function, const char* name);
|
||||
~worker_thread();
|
||||
void wake(); // causes the work_function to be called (if the thread had been sleeping)
|
||||
void join(); // asks the thread to stop and then joins it
|
||||
private:
|
||||
static void threadProc(boost::shared_ptr<data> data, const boost::function0<work_result>& work_function);
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
79
engine/core/src/ThreadSafe.cpp
Normal file
79
engine/core/src/ThreadSafe.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "boost.hpp"
|
||||
#include "threadsafe.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include "atomic.hpp"
|
||||
|
||||
|
||||
|
||||
Aya::concurrency_catcher::scoped_lock::scoped_lock(concurrency_catcher& m)
|
||||
: m(m)
|
||||
{
|
||||
if (m.value.swap(concurrency_catcher::locked) != concurrency_catcher::unlocked)
|
||||
{
|
||||
AYACRASH();
|
||||
}
|
||||
}
|
||||
|
||||
Aya::concurrency_catcher::scoped_lock::~scoped_lock()
|
||||
{
|
||||
m.value.swap(concurrency_catcher::unlocked);
|
||||
}
|
||||
|
||||
const unsigned long Aya::reentrant_concurrency_catcher::noThreadId = 4493024;
|
||||
|
||||
Aya::reentrant_concurrency_catcher::scoped_lock::scoped_lock(Aya::reentrant_concurrency_catcher& m)
|
||||
: m(m)
|
||||
{
|
||||
const long threadId(GetCurrentThreadId());
|
||||
isChild = threadId == m.threadId;
|
||||
if (isChild)
|
||||
return; // Ah ha! I'm a recursive lock request. Just go ahead :)
|
||||
|
||||
if (m.value.swap(reentrant_concurrency_catcher::locked) != reentrant_concurrency_catcher::unlocked)
|
||||
{
|
||||
AYACRASH();
|
||||
}
|
||||
|
||||
// We own the lock, so assign the current thread ID
|
||||
AYAASSERT(m.threadId == reentrant_concurrency_catcher::noThreadId);
|
||||
m.threadId = threadId;
|
||||
}
|
||||
|
||||
|
||||
Aya::reentrant_concurrency_catcher::scoped_lock::~scoped_lock()
|
||||
{
|
||||
if (!isChild)
|
||||
{
|
||||
m.threadId = reentrant_concurrency_catcher::noThreadId;
|
||||
m.value.swap(reentrant_concurrency_catcher::unlocked);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Aya::readwrite_concurrency_catcher::scoped_write_request::scoped_write_request(readwrite_concurrency_catcher& m)
|
||||
: m(m)
|
||||
{
|
||||
if (m.write_requested.swap(readwrite_concurrency_catcher::locked) != readwrite_concurrency_catcher::unlocked)
|
||||
AYAASSERT(false); // should be a AYACRASH();
|
||||
if (m.read_requested > 0)
|
||||
AYAASSERT(false); // should be a AYACRASH();
|
||||
}
|
||||
Aya::readwrite_concurrency_catcher::scoped_write_request::~scoped_write_request()
|
||||
{
|
||||
if (m.write_requested.swap(readwrite_concurrency_catcher::unlocked) != readwrite_concurrency_catcher::locked)
|
||||
AYAASSERT(false); // should be a AYACRASH();
|
||||
}
|
||||
|
||||
// Place this code around tasks that write to a DataModel
|
||||
Aya::readwrite_concurrency_catcher::scoped_read_request::scoped_read_request(readwrite_concurrency_catcher& m)
|
||||
: m(m)
|
||||
{
|
||||
if (m.write_requested != readwrite_concurrency_catcher::unlocked)
|
||||
AYAASSERT(false); // should be a AYACRASH();
|
||||
++m.read_requested;
|
||||
}
|
||||
Aya::readwrite_concurrency_catcher::scoped_read_request::~scoped_read_request()
|
||||
{
|
||||
if (--m.read_requested < 0)
|
||||
AYAASSERT(false); // should be a AYACRASH();
|
||||
}
|
||||
11
engine/core/src/XStudioBuild.hpp
Normal file
11
engine/core/src/XStudioBuild.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
// This file is used only to build a very special in-house version of Roblox Studio needed to develop Xbox-specific CoreScripts.
|
||||
|
||||
// Set to 1 to enable the special build. DO NOT SUBMIT TO CI OR TRUNK WITH THIS SET TO ON.
|
||||
#define ENABLE_XBOX_STUDIO_BUILD 0
|
||||
|
||||
|
||||
#ifndef _MSC_VER // visual studio only
|
||||
#undef ENABLE_XBOX_STUDIO_BUILD
|
||||
#define ENABLE_XBOX_STUDIO_BUILD 0
|
||||
#endif
|
||||
127
engine/core/src/atomic.hpp
Normal file
127
engine/core/src/atomic.hpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(AYA_PLATFORM_IOS) || defined(__APPLE__) || __ANDROID__ || __linux || __APPLE__
|
||||
namespace Aya
|
||||
{
|
||||
template<typename T>
|
||||
class atomic
|
||||
{
|
||||
T v;
|
||||
|
||||
public:
|
||||
atomic(T value = 0)
|
||||
: v(value)
|
||||
{
|
||||
}
|
||||
|
||||
T operator=(T v)
|
||||
{
|
||||
this->v = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
T compare_and_swap(T value, T comparand)
|
||||
{
|
||||
return __sync_val_compare_and_swap(&v, comparand, value);
|
||||
}
|
||||
|
||||
T operator++()
|
||||
{
|
||||
return __sync_add_and_fetch(&v, 1);
|
||||
}
|
||||
|
||||
T operator--()
|
||||
{
|
||||
return __sync_sub_and_fetch(&v, 1);
|
||||
}
|
||||
|
||||
T operator++(int)
|
||||
{
|
||||
return __sync_fetch_and_add(&v, 1);
|
||||
}
|
||||
|
||||
T operator--(int)
|
||||
{
|
||||
return __sync_fetch_and_sub(&v, 1);
|
||||
}
|
||||
|
||||
T swap(T value)
|
||||
{
|
||||
return __sync_lock_test_and_set(&v, value);
|
||||
}
|
||||
};
|
||||
} // namespace Aya
|
||||
|
||||
#elif defined(_WIN32) // Windows
|
||||
|
||||
#include "Debug.hpp"
|
||||
#include "boost/detail/interlocked.hpp"
|
||||
#include "boost/static_assert.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
template<typename T>
|
||||
class atomic
|
||||
{
|
||||
BOOST_STATIC_ASSERT(sizeof(T) == sizeof(long));
|
||||
long v;
|
||||
|
||||
public:
|
||||
atomic(T value = 0)
|
||||
: v(value)
|
||||
{
|
||||
// http://msdn.microsoft.com/en-us/library/ms683614.aspx: The variable pointed to must be aligned on a 32-bit boundary
|
||||
AYAASSERT((((uintptr_t)(&(this->v))) & (sizeof(T) - 1)) == 0);
|
||||
}
|
||||
|
||||
T operator=(T v)
|
||||
{
|
||||
this->v = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
T compare_and_swap(T value, T comparand)
|
||||
{
|
||||
return BOOST_INTERLOCKED_COMPARE_EXCHANGE(&v, value, comparand);
|
||||
}
|
||||
|
||||
T operator++()
|
||||
{
|
||||
return BOOST_INTERLOCKED_INCREMENT(&v);
|
||||
}
|
||||
|
||||
T operator--()
|
||||
{
|
||||
return BOOST_INTERLOCKED_DECREMENT(&v);
|
||||
}
|
||||
|
||||
T operator++(int)
|
||||
{
|
||||
return BOOST_INTERLOCKED_INCREMENT(&v) - 1;
|
||||
}
|
||||
|
||||
T operator--(int)
|
||||
{
|
||||
return BOOST_INTERLOCKED_DECREMENT(&v) + 1;
|
||||
}
|
||||
|
||||
T swap(T value)
|
||||
{
|
||||
return BOOST_INTERLOCKED_EXCHANGE(&v, value);
|
||||
}
|
||||
};
|
||||
} // namespace Aya
|
||||
#else // you are using the wrong atomic
|
||||
#error "not supported"
|
||||
#endif
|
||||
171
engine/core/src/boost.cpp
Normal file
171
engine/core/src/boost.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "boost.hpp"
|
||||
#include "Thread.hpp"
|
||||
|
||||
#include "AyaPlatform.hpp"
|
||||
|
||||
#include <boost/type_traits.hpp>
|
||||
#include <boost/math/special_functions/fpclassify.hpp>
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <pthread.h>
|
||||
#else
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
bool isFinite(double value)
|
||||
{
|
||||
return boost::math::isfinite(value);
|
||||
}
|
||||
|
||||
bool isFinite(int value)
|
||||
{
|
||||
return boost::math::isfinite(value);
|
||||
}
|
||||
|
||||
bool nameThreads = true;
|
||||
|
||||
namespace boost_detail
|
||||
{
|
||||
boost::once_flag once_init_foo = BOOST_ONCE_INIT;
|
||||
static boost::thread_specific_ptr<std::string>* foo;
|
||||
void init_foo(void)
|
||||
{
|
||||
static boost::thread_specific_ptr<std::string> value;
|
||||
foo = &value;
|
||||
}
|
||||
|
||||
static boost::thread_specific_ptr<std::string>& threadName()
|
||||
{
|
||||
boost::call_once(init_foo, once_init_foo);
|
||||
return *foo;
|
||||
}
|
||||
} // namespace boost_detail
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 6312)
|
||||
#pragma warning(disable : 6322)
|
||||
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // must be 0x1000
|
||||
LPCSTR szName; // pointer to name (in user addr space)
|
||||
DWORD dwThreadID; // thread ID (-1=caller thread)
|
||||
DWORD dwFlags; // reserved for future use, must be zero
|
||||
} THREADNAME_INFO;
|
||||
|
||||
void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName)
|
||||
{
|
||||
if (nameThreads)
|
||||
{
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = szThreadName;
|
||||
info.dwThreadID = dwThreadID;
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
// Comment this out to allow AQ time to work per Erik - 5/22/07
|
||||
// Search on AQTime, Profiling, VTune, Performance, threadName - to find this
|
||||
// TODO: Put an early check in DllMain to set nameThreads off if you pass in some cmd-line argument
|
||||
|
||||
RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except (EXCEPTION_CONTINUE_EXECUTION)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
void set_thread_name(const char* name)
|
||||
{
|
||||
Aya::boost_detail::threadName().reset(new std::string(name));
|
||||
#ifdef _WIN32
|
||||
Aya::SetThreadName(GetCurrentThreadId(), name);
|
||||
#else
|
||||
#ifdef __linux__
|
||||
if (nameThreads)
|
||||
::pthread_setname_np(pthread_self(), name);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* get_thread_name()
|
||||
{
|
||||
static const char* none = "unnamed thread"; // an alphanumeric default string
|
||||
std::string* result = Aya::boost_detail::threadName().get();
|
||||
return result ? result->c_str() : none;
|
||||
}
|
||||
|
||||
static void thread_function(const boost::function0<void>& threadfunc, std::string name)
|
||||
{
|
||||
set_thread_name(name.c_str());
|
||||
threadfunc();
|
||||
}
|
||||
|
||||
boost::function0<void> thread_wrapper(const boost::function0<void>& threadfunc, const char* name)
|
||||
{
|
||||
// We wrap "name" with a std::string to ensure the buffer is not lost
|
||||
return boost::bind(&thread_function, threadfunc, std::string(name));
|
||||
}
|
||||
|
||||
worker_thread::worker_thread(const boost::function0<work_result>& work_function, const char* name)
|
||||
: _data(new data())
|
||||
// Start the thread
|
||||
, t(thread_wrapper(boost::bind(&threadProc, _data, work_function), name))
|
||||
{
|
||||
}
|
||||
|
||||
worker_thread::~worker_thread()
|
||||
{
|
||||
boost::mutex::scoped_lock lock(_data->sync);
|
||||
_data->endRequest = true;
|
||||
_data->wakeCondition.notify_all();
|
||||
// _data will get collected when threadProc exits
|
||||
}
|
||||
|
||||
void worker_thread::join()
|
||||
{
|
||||
{
|
||||
boost::mutex::scoped_lock lock(_data->sync);
|
||||
_data->endRequest = true;
|
||||
_data->wakeCondition.notify_all();
|
||||
}
|
||||
t.join();
|
||||
}
|
||||
|
||||
void worker_thread::wake()
|
||||
{
|
||||
boost::mutex::scoped_lock lock(_data->sync);
|
||||
_data->wakeCondition.notify_all();
|
||||
}
|
||||
|
||||
void worker_thread::threadProc(boost::shared_ptr<data> data, const boost::function0<work_result>& work_function)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Do all open work unless an end is requested
|
||||
do
|
||||
{
|
||||
if (data->endRequest)
|
||||
return;
|
||||
} while (work_function() == more);
|
||||
|
||||
// Sleep until a request for more work comes through
|
||||
{
|
||||
boost::mutex::scoped_lock lock(data->sync);
|
||||
data->wakeCondition.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
// "data" will get collected when worker_thread is destroyed
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
220
engine/core/src/boost.hpp
Normal file
220
engine/core/src/boost.hpp
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include <algorithm> // defines std::min and std::max before Windows.h takes over
|
||||
|
||||
#include <boost/config/user.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/weak_ptr.hpp>
|
||||
|
||||
// for placement_any
|
||||
#include <boost/type_traits/remove_reference.hpp>
|
||||
#include <boost/type_traits/is_reference.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
using boost::scoped_ptr;
|
||||
using boost::shared_ptr;
|
||||
using boost::weak_ptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
// #include <Windows.h>
|
||||
#else
|
||||
#include "AyaFormat.hpp"
|
||||
#include <pthread.h>
|
||||
// This is a hack. Truncates a pointer.
|
||||
#if defined(__linux)
|
||||
#define GetCurrentThreadId() (static_cast<unsigned long>(pthread_self()))
|
||||
#else
|
||||
#define GetCurrentThreadId() (static_cast<unsigned>(reinterpret_cast<long>(pthread_self())))
|
||||
#endif
|
||||
#define SwitchToThread() \
|
||||
{ \
|
||||
sched_yield(); \
|
||||
}
|
||||
// We may decide to use the following instead on Mac, but we would prefer the above.
|
||||
// #define SwitchToThread() {struct timespec req = {0, 1}; nanosleep(&req, NULL);}
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// TODO: Does boost have a nicer way of doing this?
|
||||
template<class T>
|
||||
void del_fun(T* t)
|
||||
{
|
||||
delete t;
|
||||
}
|
||||
|
||||
bool isFinite(double value);
|
||||
bool isFinite(int value);
|
||||
} // namespace Aya
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
|
||||
namespace implementation
|
||||
{
|
||||
class type_holder : boost::noncopyable
|
||||
{
|
||||
public: // operations
|
||||
void (*destruct)(char* dest);
|
||||
void (*construct)(const char* src, char* dest);
|
||||
};
|
||||
|
||||
template<typename ValueType>
|
||||
class typed_holder : public type_holder
|
||||
{
|
||||
typed_holder()
|
||||
{
|
||||
construct = &construct_func;
|
||||
destruct = &destruct_func;
|
||||
}
|
||||
|
||||
public:
|
||||
static const typed_holder* singleton()
|
||||
{
|
||||
static typed_holder<ValueType> s;
|
||||
return &s;
|
||||
}
|
||||
|
||||
static void construct_func(const char* src, char* dest)
|
||||
{
|
||||
const ValueType* value = reinterpret_cast<const ValueType*>(src);
|
||||
ValueType* v = reinterpret_cast<ValueType*>(dest);
|
||||
new (v) ValueType(*value);
|
||||
}
|
||||
|
||||
static void destruct_func(char* dest)
|
||||
{
|
||||
ValueType* value = reinterpret_cast<ValueType*>(dest);
|
||||
value->~ValueType();
|
||||
}
|
||||
};
|
||||
} // namespace implementation
|
||||
|
||||
// placement_any is a reworking of boost::any that embeds the value inside of
|
||||
// itself, rather than in the heap. This eliminates new/delete operations. However,
|
||||
// you must know in advance how big the values are able to be. Also, placement_any
|
||||
// allocates enough memory for the largest possible object, even when it is void.
|
||||
// SizeType must be a class that is as large as the largest sized object that
|
||||
// will be placed inside placement_any
|
||||
template<typename SizeType>
|
||||
class placement_any
|
||||
{
|
||||
public: // structors
|
||||
placement_any()
|
||||
: holder(0)
|
||||
{
|
||||
}
|
||||
|
||||
placement_any(const placement_any& other)
|
||||
: holder(0)
|
||||
{
|
||||
if (other.holder)
|
||||
(*other.holder->construct)(other.data, data);
|
||||
holder = other.holder; // construct didn't throw, so we can assign the holder now
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
explicit placement_any(const ValueType& value)
|
||||
: holder(0)
|
||||
{
|
||||
// If this fails, then make ValueType the new SizeType!
|
||||
BOOST_STATIC_ASSERT((sizeof(ValueType) <= sizeof(SizeType)));
|
||||
|
||||
ValueType* v = reinterpret_cast<ValueType*>(data);
|
||||
new (v) ValueType(value);
|
||||
holder = implementation::typed_holder<ValueType>::singleton(); // construct didn't throw, so we can assign it now
|
||||
}
|
||||
|
||||
~placement_any()
|
||||
{
|
||||
if (holder)
|
||||
(*holder->destruct)(data);
|
||||
}
|
||||
|
||||
public: // modifiers
|
||||
placement_any& swap(placement_any& rhs)
|
||||
{
|
||||
placement_any temp(*this);
|
||||
*this = rhs;
|
||||
rhs = temp;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
placement_any& operator=(const ValueType& rhs)
|
||||
{
|
||||
const implementation::typed_holder<ValueType>* s = implementation::typed_holder<ValueType>::singleton();
|
||||
if (holder == s)
|
||||
{
|
||||
// Optimization. Is this worth it?
|
||||
ValueType* dest = reinterpret_cast<ValueType*>(data);
|
||||
*dest = rhs;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (holder)
|
||||
{
|
||||
(*holder->destruct)(data);
|
||||
holder = 0;
|
||||
}
|
||||
|
||||
// If this fails, then make ValueType the new SizeType!
|
||||
BOOST_STATIC_ASSERT((sizeof(ValueType) <= sizeof(SizeType)));
|
||||
|
||||
ValueType* v = reinterpret_cast<ValueType*>(data);
|
||||
new (v) ValueType(rhs);
|
||||
|
||||
holder = s;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
placement_any& operator=(const placement_any& rhs)
|
||||
{
|
||||
if (&rhs == this)
|
||||
return *this;
|
||||
|
||||
if (holder)
|
||||
{
|
||||
(*holder->destruct)(data);
|
||||
holder = 0;
|
||||
}
|
||||
if (rhs.holder)
|
||||
{
|
||||
(*rhs.holder->construct)(rhs.data, data);
|
||||
holder = rhs.holder;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
public: // queries
|
||||
bool empty() const
|
||||
{
|
||||
return holder == 0;
|
||||
}
|
||||
|
||||
const char* getData() const
|
||||
{
|
||||
return holder ? data : NULL;
|
||||
}
|
||||
|
||||
char* getData()
|
||||
{
|
||||
return holder ? data : NULL;
|
||||
}
|
||||
|
||||
private: // representation
|
||||
const implementation::type_holder* holder;
|
||||
char data[sizeof(SizeType)];
|
||||
};
|
||||
} // namespace Aya
|
||||
237
engine/core/src/callable.hpp
Normal file
237
engine/core/src/callable.hpp
Normal file
@@ -0,0 +1,237 @@
|
||||
|
||||
#include "boost/type_traits/function_traits.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// icallable and callable are a lightweight wrapper similar to boost::function,
|
||||
// but with more efficient storage. Because the instances are special-cased for
|
||||
// the functor involved you can't treat callable generically - it has to be
|
||||
// used in the context of template code that knows what to do with it.
|
||||
|
||||
// See Aya::signals for an implementation that uses this in lieu of boost::function
|
||||
|
||||
template<int arity, typename Signature>
|
||||
class icallable;
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<0, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call() = 0;
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<1, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1) = 0;
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<2, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2) = 0;
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<3, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3) = 0;
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<4, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4) = 0;
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<5, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5) = 0;
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<6, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5, typename boost::function_traits<Signature>::arg6_type arg6) = 0;
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class icallable<7, Signature>
|
||||
{
|
||||
public:
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5, typename boost::function_traits<Signature>::arg6_type arg6,
|
||||
typename boost::function_traits<Signature>::arg7_type arg7) = 0;
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, int arity, typename Signature>
|
||||
class callable;
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 0, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& d, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(d)
|
||||
{
|
||||
}
|
||||
virtual void call()
|
||||
{
|
||||
deleg();
|
||||
}
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 1, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& deleg, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(deleg)
|
||||
{
|
||||
}
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1)
|
||||
{
|
||||
deleg(arg1);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 2, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& deleg, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(deleg)
|
||||
{
|
||||
}
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2)
|
||||
{
|
||||
deleg(arg1, arg2);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 3, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& deleg, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(deleg)
|
||||
{
|
||||
}
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3)
|
||||
{
|
||||
deleg(arg1, arg2, arg3);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 4, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& deleg, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(deleg)
|
||||
{
|
||||
}
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4)
|
||||
{
|
||||
deleg(arg1, arg2, arg3, arg4);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 5, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& deleg, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(deleg)
|
||||
{
|
||||
}
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5)
|
||||
{
|
||||
deleg(arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 6, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& deleg, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(deleg)
|
||||
{
|
||||
}
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5, typename boost::function_traits<Signature>::arg6_type arg6)
|
||||
{
|
||||
deleg(arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Base, class Delegate, typename Signature>
|
||||
class callable<Base, Delegate, 7, Signature> : public Base
|
||||
{
|
||||
Delegate deleg;
|
||||
|
||||
public:
|
||||
template<typename Arg1>
|
||||
callable(const Delegate& deleg, Arg1 arg1)
|
||||
: Base(arg1)
|
||||
, deleg(deleg)
|
||||
{
|
||||
}
|
||||
virtual void call(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5, typename boost::function_traits<Signature>::arg6_type arg6,
|
||||
typename boost::function_traits<Signature>::arg7_type arg7)
|
||||
{
|
||||
deleg(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
286
engine/core/src/format_string.cpp
Normal file
286
engine/core/src/format_string.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#ifdef WIN32
|
||||
#include <Objbase.h>
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include "format_string.hpp"
|
||||
|
||||
#ifdef __linux
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__ANDROID__)
|
||||
inline int _vscprintf(const char* format, va_list argptr)
|
||||
{
|
||||
return vsnprintf(NULL, 0, format, argptr);
|
||||
}
|
||||
static const size_t _TRUNCATE = 0;
|
||||
inline int vsnprintf_s(char* buffer, size_t sizeOfBuffer, size_t count, const char* format, va_list argptr)
|
||||
{
|
||||
return vsnprintf(buffer, sizeOfBuffer, format, argptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string convert_w2s(const std::wstring& str)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::string result;
|
||||
LPSTR szText = new CHAR[str.size() + 1];
|
||||
WideCharToMultiByte(CP_ACP, 0, str.c_str(), -1, szText, (int)str.size() + 1, 0, 0);
|
||||
result = szText;
|
||||
delete[] szText;
|
||||
return result;
|
||||
#else
|
||||
std::string result;
|
||||
std::copy(str.begin(), str.end(), std::back_inserter(result));
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::wstring convert_s2w(const std::string& str)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::wstring result;
|
||||
LPWSTR szTextW = new WCHAR[str.size() + 1];
|
||||
MultiByteToWideChar(0, 0, str.c_str(), -1, szTextW, (int)str.size() + 1);
|
||||
result = szTextW;
|
||||
delete[] szTextW;
|
||||
return result;
|
||||
#else
|
||||
std::wstring result;
|
||||
std::copy(str.begin(), str.end(), std::back_inserter(result));
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef __linux
|
||||
std::string vformat(const char* fmt, va_list argPtr)
|
||||
{
|
||||
// We draw the line at a 1MB string.
|
||||
const int maxSize = 1000000;
|
||||
|
||||
// If the string is less than 161 characters,
|
||||
// allocate it on the stack because this saves
|
||||
// the malloc/free time.
|
||||
const int bufSize = 161;
|
||||
char stackBuffer[bufSize];
|
||||
|
||||
int actualSize = _vscprintf(fmt, argPtr) + 1;
|
||||
|
||||
if (actualSize > bufSize)
|
||||
{
|
||||
|
||||
// Now use the heap.
|
||||
char* heapBuffer = NULL;
|
||||
|
||||
if (actualSize < maxSize)
|
||||
{
|
||||
|
||||
heapBuffer = (char*)malloc(maxSize + 1);
|
||||
if (!heapBuffer)
|
||||
throw std::bad_alloc();
|
||||
vsnprintf_s(heapBuffer, maxSize, _TRUNCATE, fmt, argPtr);
|
||||
heapBuffer[maxSize] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
heapBuffer = (char*)malloc(actualSize);
|
||||
vsnprintf_s(heapBuffer, actualSize - 1, _TRUNCATE, fmt, argPtr);
|
||||
heapBuffer[actualSize - 1] = '\0';
|
||||
}
|
||||
|
||||
std::string formattedString(heapBuffer);
|
||||
free(heapBuffer);
|
||||
return formattedString;
|
||||
}
|
||||
else
|
||||
{
|
||||
vsnprintf_s(stackBuffer, bufSize - 1, _TRUNCATE, fmt, argPtr);
|
||||
stackBuffer[bufSize - 1] = '\0';
|
||||
return std::string(stackBuffer);
|
||||
}
|
||||
}
|
||||
#else
|
||||
std::string vformat(const char* fmt, va_list argPtr)
|
||||
{
|
||||
const int maxSize = 1000000;
|
||||
const int bufSize = 161;
|
||||
char stackBuffer[bufSize];
|
||||
char* heapBuffer = nullptr;
|
||||
std::string formattedString;
|
||||
|
||||
// Using va_copy to safely use the argPtr again after vsnprintf for determining the buffer size
|
||||
va_list argPtrCopy;
|
||||
va_copy(argPtrCopy, argPtr);
|
||||
int requiredSize = vsnprintf(nullptr, 0, fmt, argPtrCopy) + 1; // +1 for the null-terminator
|
||||
va_end(argPtrCopy);
|
||||
|
||||
if (requiredSize > bufSize)
|
||||
{
|
||||
int bufferSize = requiredSize < maxSize ? maxSize : requiredSize;
|
||||
heapBuffer = static_cast<char*>(malloc(bufferSize));
|
||||
if (!heapBuffer)
|
||||
throw std::bad_alloc();
|
||||
|
||||
vsnprintf(heapBuffer, bufferSize, fmt, argPtr);
|
||||
formattedString.assign(heapBuffer);
|
||||
free(heapBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
vsnprintf(stackBuffer, bufSize, fmt, argPtr);
|
||||
formattedString.assign(stackBuffer);
|
||||
}
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef __linux
|
||||
std::string format_string(const char* fmt, ...)
|
||||
{
|
||||
va_list argList;
|
||||
va_start(argList, fmt);
|
||||
std::string result = vformat(fmt, argList);
|
||||
va_end(argList);
|
||||
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
std::string format_string(const char* fmt, ...)
|
||||
{
|
||||
va_list argList;
|
||||
va_start(argList, fmt);
|
||||
std::string result = vformat(fmt, argList);
|
||||
va_end(argList);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
std::wstring vformat(const wchar_t* fmt, va_list argPtr)
|
||||
{
|
||||
// We draw the line at a 1MB string.
|
||||
const int maxSize = 1000000;
|
||||
|
||||
// If the string is less than 161 characters,
|
||||
// allocate it on the stack because this saves
|
||||
// the malloc/free time.
|
||||
const int bufSize = 161;
|
||||
wchar_t stackBuffer[bufSize];
|
||||
|
||||
int actualSize = _vscwprintf(fmt, argPtr) + 1;
|
||||
|
||||
if (actualSize > bufSize)
|
||||
{
|
||||
|
||||
// Now use the heap.
|
||||
wchar_t* heapBuffer = NULL;
|
||||
|
||||
if (actualSize < maxSize)
|
||||
{
|
||||
|
||||
heapBuffer = (wchar_t*)malloc((maxSize + 1) * sizeof(wchar_t));
|
||||
if (!heapBuffer)
|
||||
throw std::bad_alloc();
|
||||
_vsnwprintf_s(heapBuffer, maxSize, _TRUNCATE, fmt, argPtr);
|
||||
heapBuffer[maxSize] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
heapBuffer = (wchar_t*)malloc(actualSize * sizeof(wchar_t));
|
||||
_vsnwprintf_s(heapBuffer, actualSize - 1, _TRUNCATE, fmt, argPtr);
|
||||
heapBuffer[actualSize - 1] = '\0';
|
||||
}
|
||||
|
||||
std::wstring formattedString(heapBuffer);
|
||||
free(heapBuffer);
|
||||
return formattedString;
|
||||
}
|
||||
else
|
||||
{
|
||||
_vsnwprintf_s(stackBuffer, bufSize - 1, _TRUNCATE, fmt, argPtr);
|
||||
stackBuffer[bufSize - 1] = '\0';
|
||||
return std::wstring(stackBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring format_string(const wchar_t* fmt, ...)
|
||||
{
|
||||
va_list argList;
|
||||
va_start(argList, fmt);
|
||||
std::wstring result = vformat(fmt, argList);
|
||||
va_end(argList);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<std::string> splitOn(const std::string& str, const char& delimeter, const bool trimEmpty)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = str.find(delimeter);
|
||||
|
||||
if (end != std::string::npos)
|
||||
{
|
||||
while (end != std::string::npos)
|
||||
{
|
||||
std::string tmp = str.substr(begin, end - begin);
|
||||
if (!tmp.empty() || !trimEmpty)
|
||||
tokens.push_back(tmp);
|
||||
|
||||
begin = end + 1;
|
||||
end = str.find(delimeter, begin);
|
||||
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
tmp = str.substr(begin, str.length() - begin);
|
||||
if (!tmp.empty() || !trimEmpty)
|
||||
tokens.push_back(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!str.empty() || !trimEmpty)
|
||||
tokens.push_back(str);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
std::vector<std::wstring> splitOn(const std::wstring& str, const wchar_t& delimeter, const bool trimEmpty)
|
||||
{
|
||||
std::vector<std::wstring> tokens;
|
||||
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = str.find(delimeter);
|
||||
|
||||
if (end != std::wstring::npos)
|
||||
{
|
||||
while (end != std::wstring::npos)
|
||||
{
|
||||
std::wstring tmp = str.substr(begin, end - begin);
|
||||
if (!tmp.empty() || !trimEmpty)
|
||||
tokens.push_back(tmp);
|
||||
|
||||
begin = end + 1;
|
||||
end = str.find(delimeter, begin);
|
||||
|
||||
if (end == std::wstring::npos)
|
||||
{
|
||||
tmp = str.substr(begin, str.length() - begin);
|
||||
if (!tmp.empty() || !trimEmpty)
|
||||
tokens.push_back(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!str.empty() || !trimEmpty)
|
||||
tokens.push_back(str);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
139
engine/core/src/format_string.hpp
Normal file
139
engine/core/src/format_string.hpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <combaseapi.h>
|
||||
#else
|
||||
#include <cstdarg>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
std::string vformat(const char* fmt, va_list argPtr);
|
||||
std::wstring vformat(const wchar_t* fmt, va_list argPtr);
|
||||
|
||||
std::string format_string(const char* fmt, ...);
|
||||
std::wstring format_string(const wchar_t* fmt, ...);
|
||||
|
||||
std::string convert_w2s(const std::wstring& str);
|
||||
std::wstring convert_s2w(const std::string& str);
|
||||
|
||||
std::vector<std::string> splitOn(const std::string& str, const char& delimeter, const bool trimEmpty = true);
|
||||
|
||||
std::vector<std::wstring> splitOn(const std::wstring& str, const wchar_t& delimeter, const bool trimEmpty = true);
|
||||
|
||||
#ifdef UNICODE
|
||||
#define CVTW2S(value) convert_w2s(value)
|
||||
#else
|
||||
#define CVTW2S(value) (value)
|
||||
#endif
|
||||
|
||||
#ifdef UNICODE
|
||||
#define CVTS2W(value) convert_s2w(value)
|
||||
#else
|
||||
#define CVTS2W(value) (value)
|
||||
#endif
|
||||
|
||||
#ifdef UNICODE
|
||||
#define MAKE_STRING(value) std::wstring(L##value)
|
||||
#else
|
||||
#define MAKE_STRING(value) std::string(value)
|
||||
#endif
|
||||
|
||||
template<class CHARTYPE>
|
||||
class simple_logger
|
||||
{
|
||||
typedef std::basic_string<CHARTYPE, std::char_traits<CHARTYPE>, std::allocator<CHARTYPE>> STRING;
|
||||
|
||||
private:
|
||||
void write_logentry_raw(const char* entry)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SYSTEMTIME sysTime = {};
|
||||
GetSystemTime(&sysTime);
|
||||
char timeBuf[128];
|
||||
sprintf_s(timeBuf, 128, "%02d-%02d-%d %02d:%02d:%02d.%d ", sysTime.wMonth, sysTime.wDay, sysTime.wYear, sysTime.wHour, sysTime.wMinute,
|
||||
sysTime.wSecond, sysTime.wMilliseconds);
|
||||
log << timeBuf << entry << "\n";
|
||||
log.flush();
|
||||
#endif
|
||||
}
|
||||
|
||||
STRING logFileName;
|
||||
std::ofstream log;
|
||||
|
||||
public:
|
||||
simple_logger()
|
||||
: logFileName(get_temp_filename(MAKE_STRING("log").c_str()))
|
||||
, log(logFileName.c_str())
|
||||
{
|
||||
}
|
||||
|
||||
simple_logger(const CHARTYPE* fileName)
|
||||
: logFileName(fileName)
|
||||
, log(logFileName.c_str())
|
||||
{
|
||||
}
|
||||
|
||||
static STRING get_tmp_path()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CHARTYPE szPath[_MAX_DIR];
|
||||
if (!GetTempPathA(_MAX_DIR, szPath))
|
||||
{
|
||||
throw std::runtime_error("GetTempPath in simple_logger::getTempFileName failed");
|
||||
}
|
||||
return STRING(szPath);
|
||||
#else
|
||||
return STRING(MAKE_STRING("/tmp/"));
|
||||
#endif
|
||||
}
|
||||
|
||||
static STRING get_temp_filename(const CHARTYPE* ext)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
GUID g;
|
||||
::CoCreateGuid(&g);
|
||||
char szFilePath[MAX_PATH];
|
||||
sprintf_s(szFilePath, MAX_PATH, "%SRBX-%08X.%S", get_tmp_path().c_str(), g.Data1, ext);
|
||||
|
||||
return STRING(CVTS2W(szFilePath));
|
||||
#else
|
||||
// Unix does not have GUIDs like Windows, so use a unique filename creation mechanism
|
||||
std::string tmpFileName = "/tmp/logXXXXXX"; // Template for mkstemp
|
||||
int tmpFd = mkstemp(&tmpFileName[0]); // Creates a unique temporary file name and opens it
|
||||
if (tmpFd < 0)
|
||||
{
|
||||
throw std::runtime_error("Unable to create temp file in simple_logger::get_temp_filename");
|
||||
}
|
||||
close(tmpFd); // Close the file descriptor, we just want the name
|
||||
|
||||
// Append the extension
|
||||
tmpFileName += ".";
|
||||
tmpFileName += ext;
|
||||
return STRING(tmpFileName.begin(), tmpFileName.end()); // Convert to the desired character type
|
||||
#endif
|
||||
}
|
||||
|
||||
void write_logentry(const char* format, ...)
|
||||
{
|
||||
va_list argList;
|
||||
va_start(argList, format); // Now va_start should be recognized
|
||||
std::string result = vformat(format, argList);
|
||||
write_logentry_raw(result.c_str());
|
||||
va_end(argList); // And va_end should be recognized as well
|
||||
}
|
||||
|
||||
STRING& log_filename()
|
||||
{
|
||||
return logFileName;
|
||||
}
|
||||
};
|
||||
658
engine/core/src/ifaddrs.cpp
Normal file
658
engine/core/src/ifaddrs.cpp
Normal file
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
Copyright (c) 2013, Kenneth MacKay
|
||||
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.
|
||||
|
||||
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 HOLDER 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.
|
||||
*/
|
||||
|
||||
#include "ifaddrs.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
typedef struct NetlinkList
|
||||
{
|
||||
struct NetlinkList *m_next;
|
||||
struct nlmsghdr *m_data;
|
||||
unsigned int m_size;
|
||||
} NetlinkList;
|
||||
|
||||
static int netlink_socket(void)
|
||||
{
|
||||
int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
if(l_socket < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_nl l_addr;
|
||||
memset(&l_addr, 0, sizeof(l_addr));
|
||||
l_addr.nl_family = AF_NETLINK;
|
||||
if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0)
|
||||
{
|
||||
close(l_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return l_socket;
|
||||
}
|
||||
|
||||
static int netlink_send(int p_socket, int p_request)
|
||||
{
|
||||
char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))];
|
||||
memset(l_buffer, 0, sizeof(l_buffer));
|
||||
struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer;
|
||||
struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr);
|
||||
|
||||
l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg));
|
||||
l_hdr->nlmsg_type = p_request;
|
||||
l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
|
||||
l_hdr->nlmsg_pid = 0;
|
||||
l_hdr->nlmsg_seq = p_socket;
|
||||
l_msg->rtgen_family = AF_UNSPEC;
|
||||
|
||||
struct sockaddr_nl l_addr;
|
||||
memset(&l_addr, 0, sizeof(l_addr));
|
||||
l_addr.nl_family = AF_NETLINK;
|
||||
return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr)));
|
||||
}
|
||||
|
||||
static int netlink_recv(int p_socket, void *p_buffer, size_t p_len)
|
||||
{
|
||||
struct msghdr l_msg;
|
||||
struct iovec l_iov = { p_buffer, p_len };
|
||||
struct sockaddr_nl l_addr;
|
||||
int l_result;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
l_msg.msg_name = (void *)&l_addr;
|
||||
l_msg.msg_namelen = sizeof(l_addr);
|
||||
l_msg.msg_iov = &l_iov;
|
||||
l_msg.msg_iovlen = 1;
|
||||
l_msg.msg_control = NULL;
|
||||
l_msg.msg_controllen = 0;
|
||||
l_msg.msg_flags = 0;
|
||||
int l_result = recvmsg(p_socket, &l_msg, 0);
|
||||
|
||||
if(l_result < 0)
|
||||
{
|
||||
if(errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(l_msg.msg_flags & MSG_TRUNC)
|
||||
{ // buffer was too small
|
||||
return -1;
|
||||
}
|
||||
return l_result;
|
||||
}
|
||||
}
|
||||
|
||||
static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done)
|
||||
{
|
||||
size_t l_size = 4096;
|
||||
void *l_buffer = NULL;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
free(l_buffer);
|
||||
l_buffer = malloc(l_size);
|
||||
if (l_buffer == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int l_read = netlink_recv(p_socket, l_buffer, l_size);
|
||||
*p_size = l_read;
|
||||
if(l_read == -2)
|
||||
{
|
||||
free(l_buffer);
|
||||
return NULL;
|
||||
}
|
||||
if(l_read >= 0)
|
||||
{
|
||||
pid_t l_pid = getpid();
|
||||
struct nlmsghdr *l_hdr;
|
||||
for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read))
|
||||
{
|
||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
{
|
||||
*p_done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_ERROR)
|
||||
{
|
||||
free(l_buffer);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return l_buffer;
|
||||
}
|
||||
|
||||
l_size *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size)
|
||||
{
|
||||
NetlinkList *l_item = malloc(sizeof(NetlinkList));
|
||||
if (l_item == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
l_item->m_next = NULL;
|
||||
l_item->m_data = p_data;
|
||||
l_item->m_size = p_size;
|
||||
return l_item;
|
||||
}
|
||||
|
||||
static void freeResultList(NetlinkList *p_list)
|
||||
{
|
||||
NetlinkList *l_cur;
|
||||
while(p_list)
|
||||
{
|
||||
l_cur = p_list;
|
||||
p_list = p_list->m_next;
|
||||
free(l_cur->m_data);
|
||||
free(l_cur);
|
||||
}
|
||||
}
|
||||
|
||||
static NetlinkList *getResultList(int p_socket, int p_request)
|
||||
{
|
||||
if(netlink_send(p_socket, p_request) < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NetlinkList *l_list = NULL;
|
||||
NetlinkList *l_end = NULL;
|
||||
int l_size;
|
||||
int l_done = 0;
|
||||
while(!l_done)
|
||||
{
|
||||
struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done);
|
||||
if(!l_hdr)
|
||||
{ // error
|
||||
freeResultList(l_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NetlinkList *l_item = newListItem(l_hdr, l_size);
|
||||
if (!l_item)
|
||||
{
|
||||
freeResultList(l_list);
|
||||
return NULL;
|
||||
}
|
||||
if(!l_list)
|
||||
{
|
||||
l_list = l_item;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_end->m_next = l_item;
|
||||
}
|
||||
l_end = l_item;
|
||||
}
|
||||
return l_list;
|
||||
}
|
||||
|
||||
static size_t maxSize(size_t a, size_t b)
|
||||
{
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
static size_t calcAddrLen(sa_family_t p_family, int p_dataSize)
|
||||
{
|
||||
switch(p_family)
|
||||
{
|
||||
case AF_INET:
|
||||
return sizeof(struct sockaddr_in);
|
||||
case AF_INET6:
|
||||
return sizeof(struct sockaddr_in6);
|
||||
case AF_PACKET:
|
||||
return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize);
|
||||
default:
|
||||
return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size)
|
||||
{
|
||||
switch(p_family)
|
||||
{
|
||||
case AF_INET:
|
||||
memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size);
|
||||
break;
|
||||
case AF_INET6:
|
||||
memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size);
|
||||
break;
|
||||
case AF_PACKET:
|
||||
memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size);
|
||||
((struct sockaddr_ll*)p_dest)->sll_halen = p_size;
|
||||
break;
|
||||
default:
|
||||
memcpy(p_dest->sa_data, p_data, p_size);
|
||||
break;
|
||||
}
|
||||
p_dest->sa_family = p_family;
|
||||
}
|
||||
|
||||
static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry)
|
||||
{
|
||||
if(!*p_resultList)
|
||||
{
|
||||
*p_resultList = p_entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct ifaddrs *l_cur = *p_resultList;
|
||||
while(l_cur->ifa_next)
|
||||
{
|
||||
l_cur = l_cur->ifa_next;
|
||||
}
|
||||
l_cur->ifa_next = p_entry;
|
||||
}
|
||||
}
|
||||
|
||||
static int interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_resultList)
|
||||
{
|
||||
struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr);
|
||||
|
||||
size_t l_nameSize = 0;
|
||||
size_t l_addrSize = 0;
|
||||
size_t l_dataSize = 0;
|
||||
|
||||
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
||||
struct rtattr *l_rta;
|
||||
for(l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFLA_ADDRESS:
|
||||
case IFLA_BROADCAST:
|
||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize));
|
||||
break;
|
||||
case IFLA_IFNAME:
|
||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
||||
break;
|
||||
case IFLA_STATS:
|
||||
l_dataSize += NLMSG_ALIGN(l_rtaSize);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + sizeof(int) + l_nameSize + l_addrSize + l_dataSize);
|
||||
if (l_entry == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
||||
l_entry->ifa_name = "";
|
||||
|
||||
char *l_index = ((char *)l_entry) + sizeof(struct ifaddrs);
|
||||
char *l_name = l_index + sizeof(int);
|
||||
char *l_addr = l_name + l_nameSize;
|
||||
char *l_data = l_addr + l_addrSize;
|
||||
|
||||
// save the interface index so we can look it up when handling the addresses.
|
||||
memcpy(l_index, &l_info->ifi_index, sizeof(int));
|
||||
|
||||
l_entry->ifa_flags = l_info->ifi_flags;
|
||||
|
||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
||||
for(l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFLA_ADDRESS:
|
||||
case IFLA_BROADCAST:
|
||||
{
|
||||
size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize);
|
||||
makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
||||
((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index;
|
||||
((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type;
|
||||
if(l_rta->rta_type == IFLA_ADDRESS)
|
||||
{
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
l_addr += NLMSG_ALIGN(l_addrLen);
|
||||
break;
|
||||
}
|
||||
case IFLA_IFNAME:
|
||||
strncpy(l_name, l_rtaData, l_rtaDataSize);
|
||||
l_name[l_rtaDataSize] = '\0';
|
||||
l_entry->ifa_name = l_name;
|
||||
break;
|
||||
case IFLA_STATS:
|
||||
memcpy(l_data, l_rtaData, l_rtaDataSize);
|
||||
l_entry->ifa_data = l_data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addToEnd(p_resultList, l_entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ifaddrs *findInterface(int p_index, struct ifaddrs **p_links, int p_numLinks)
|
||||
{
|
||||
int l_num = 0;
|
||||
struct ifaddrs *l_cur = *p_links;
|
||||
while(l_cur && l_num < p_numLinks)
|
||||
{
|
||||
char *l_indexPtr = ((char *)l_cur) + sizeof(struct ifaddrs);
|
||||
int l_index;
|
||||
memcpy(&l_index, l_indexPtr, sizeof(int));
|
||||
if(l_index == p_index)
|
||||
{
|
||||
return l_cur;
|
||||
}
|
||||
|
||||
l_cur = l_cur->ifa_next;
|
||||
++l_num;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_resultList, int p_numLinks)
|
||||
{
|
||||
struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr);
|
||||
struct ifaddrs *l_interface = findInterface(l_info->ifa_index, p_resultList, p_numLinks);
|
||||
|
||||
size_t l_nameSize = 0;
|
||||
size_t l_addrSize = 0;
|
||||
|
||||
int l_addedNetmask = 0;
|
||||
|
||||
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
||||
struct rtattr *l_rta;
|
||||
for(l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
if(l_info->ifa_family == AF_PACKET)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFA_ADDRESS:
|
||||
case IFA_LOCAL:
|
||||
if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask)
|
||||
{ // make room for netmask
|
||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
|
||||
l_addedNetmask = 1;
|
||||
}
|
||||
case IFA_BROADCAST:
|
||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
|
||||
break;
|
||||
case IFA_LABEL:
|
||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize);
|
||||
if (l_entry == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
||||
l_entry->ifa_name = (l_interface ? l_interface->ifa_name : "");
|
||||
|
||||
char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
|
||||
char *l_addr = l_name + l_nameSize;
|
||||
|
||||
l_entry->ifa_flags = l_info->ifa_flags;
|
||||
if(l_interface)
|
||||
{
|
||||
l_entry->ifa_flags |= l_interface->ifa_flags;
|
||||
}
|
||||
|
||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
||||
for(l_rta = IFLA_RTA(l_info); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFA_ADDRESS:
|
||||
case IFA_BROADCAST:
|
||||
case IFA_LOCAL:
|
||||
{
|
||||
size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize);
|
||||
makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
||||
if(l_info->ifa_family == AF_INET6)
|
||||
{
|
||||
if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData))
|
||||
{
|
||||
((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index;
|
||||
}
|
||||
}
|
||||
|
||||
if(l_rta->rta_type == IFA_ADDRESS)
|
||||
{ // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address
|
||||
if(l_entry->ifa_addr)
|
||||
{
|
||||
l_entry->ifa_dstaddr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
}
|
||||
else if(l_rta->rta_type == IFA_LOCAL)
|
||||
{
|
||||
if(l_entry->ifa_addr)
|
||||
{
|
||||
l_entry->ifa_dstaddr = l_entry->ifa_addr;
|
||||
}
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
l_addr += NLMSG_ALIGN(l_addrLen);
|
||||
break;
|
||||
}
|
||||
case IFA_LABEL:
|
||||
strncpy(l_name, l_rtaData, l_rtaDataSize);
|
||||
l_name[l_rtaDataSize] = '\0';
|
||||
l_entry->ifa_name = l_name;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6))
|
||||
{
|
||||
unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128);
|
||||
unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen);
|
||||
char l_mask[16] = {0};
|
||||
unsigned i;
|
||||
for(i=0; i<(l_prefix/8); ++i)
|
||||
{
|
||||
l_mask[i] = 0xff;
|
||||
}
|
||||
if(l_prefix % 8)
|
||||
{
|
||||
l_mask[i] = 0xff << (8 - (l_prefix % 8));
|
||||
}
|
||||
|
||||
makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8);
|
||||
l_entry->ifa_netmask = (struct sockaddr *)l_addr;
|
||||
}
|
||||
|
||||
addToEnd(p_resultList, l_entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int interpretLinks(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_resultList)
|
||||
{
|
||||
int l_numLinks = 0;
|
||||
pid_t l_pid = getpid();
|
||||
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
||||
{
|
||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
||||
struct nlmsghdr *l_hdr;
|
||||
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
||||
{
|
||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == RTM_NEWLINK)
|
||||
{
|
||||
if(interpretLink(l_hdr, p_resultList) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
++l_numLinks;
|
||||
}
|
||||
}
|
||||
}
|
||||
return l_numLinks;
|
||||
}
|
||||
|
||||
static int interpretAddrs(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_resultList, int p_numLinks)
|
||||
{
|
||||
pid_t l_pid = getpid();
|
||||
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
||||
{
|
||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
||||
struct nlmsghdr *l_hdr;
|
||||
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
||||
{
|
||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == RTM_NEWADDR)
|
||||
{
|
||||
if (interpretAddr(l_hdr, p_resultList, p_numLinks) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getifaddrs(struct ifaddrs **ifap)
|
||||
{
|
||||
if(!ifap)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
*ifap = NULL;
|
||||
|
||||
int l_socket = netlink_socket();
|
||||
if(l_socket < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK);
|
||||
if(!l_linkResults)
|
||||
{
|
||||
close(l_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR);
|
||||
if(!l_addrResults)
|
||||
{
|
||||
close(l_socket);
|
||||
freeResultList(l_linkResults);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int l_result = 0;
|
||||
int l_numLinks = interpretLinks(l_socket, l_linkResults, ifap);
|
||||
if(l_numLinks == -1 || interpretAddrs(l_socket, l_addrResults, ifap, l_numLinks) == -1)
|
||||
{
|
||||
l_result = -1;
|
||||
}
|
||||
|
||||
freeResultList(l_linkResults);
|
||||
freeResultList(l_addrResults);
|
||||
close(l_socket);
|
||||
return l_result;
|
||||
}
|
||||
|
||||
void freeifaddrs(struct ifaddrs *ifa)
|
||||
{
|
||||
struct ifaddrs *l_cur;
|
||||
while(ifa)
|
||||
{
|
||||
l_cur = ifa;
|
||||
ifa = ifa->ifa_next;
|
||||
free(l_cur);
|
||||
}
|
||||
}
|
||||
55
engine/core/src/ifaddrs.hpp
Normal file
55
engine/core/src/ifaddrs.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 1995, 1999
|
||||
* Berkeley Software Design, 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:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``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 Berkeley Software Design, Inc. 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.
|
||||
*
|
||||
* BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp
|
||||
*/
|
||||
|
||||
#ifndef _IFADDRS_H_
|
||||
#define _IFADDRS_H_
|
||||
|
||||
struct ifaddrs
|
||||
{
|
||||
struct ifaddrs* ifa_next;
|
||||
char* ifa_name;
|
||||
unsigned int ifa_flags;
|
||||
struct sockaddr* ifa_addr;
|
||||
struct sockaddr* ifa_netmask;
|
||||
struct sockaddr* ifa_dstaddr;
|
||||
void* ifa_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* This may have been defined in <net/if.h>. Note that if <net/if.h> is
|
||||
* to be included it must be included before this header file.
|
||||
*/
|
||||
#ifndef ifa_broadaddr
|
||||
#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */
|
||||
#endif
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
extern int getifaddrs(struct ifaddrs** ifap);
|
||||
extern void freeifaddrs(struct ifaddrs* ifa);
|
||||
__END_DECLS
|
||||
|
||||
#endif
|
||||
271
engine/core/src/intrusive_ptr_target.hpp
Normal file
271
engine/core/src/intrusive_ptr_target.hpp
Normal file
@@ -0,0 +1,271 @@
|
||||
#pragma once
|
||||
|
||||
#include "Debug.hpp"
|
||||
#include "atomic.hpp"
|
||||
#include "Declarations.hpp"
|
||||
#include "boost/cast.hpp"
|
||||
#ifdef _WIN32
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
/// Forward Declarations
|
||||
namespace Aya
|
||||
{
|
||||
/*
|
||||
quick_intrusive_ptr_target<> is a mix-in that implements all functions required
|
||||
to use boost::intrusive_ptr.
|
||||
maxRefs should be used if Count is a byte or short and there is the risk of an
|
||||
overflow. Some algorithms do not have this risk. To avoid writing less performant
|
||||
code, maxRefs is not strictly enforced. In a multithreaded environment you should
|
||||
pick a "reasonable" value that is less than std::numeric_limits<Count>::max. For
|
||||
example, if Count=byte, then maxRefs could be 240
|
||||
*/
|
||||
template<class T, typename Count, Count maxRefs>
|
||||
class quick_intrusive_ptr_target;
|
||||
|
||||
/*
|
||||
intrusive_ptr_target<> is a mix-in that implements all functions required
|
||||
to use boost::intrusive_ptr and Aya::intrusive_weak_ptr. If you don't need
|
||||
weak reference support, then use quick_intrusive_ptr_target
|
||||
TODO: Allow custom allocators
|
||||
*/
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
class intrusive_ptr_target;
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
/// Template Specialization for boost intrusive ptrs
|
||||
namespace boost
|
||||
{
|
||||
/// Template Specialization for boost intrusive ptrs for quick_intrusive_ptr_target
|
||||
template<class T, typename Count, Count maxRefs>
|
||||
void intrusive_ptr_add_ref(const Aya::quick_intrusive_ptr_target<T, Count, maxRefs>* p);
|
||||
template<class T, typename Count, Count maxRefs>
|
||||
void intrusive_ptr_release(const Aya::quick_intrusive_ptr_target<T, Count, maxRefs>* p);
|
||||
|
||||
/// Template Specialization for boost intrusive ptrs for intrusive_ptr_target
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_add_ref(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_release(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
bool intrusive_ptr_expired(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
bool intrusive_ptr_try_lock(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_add_weak_ref(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_weak_release(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
} // namespace boost
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class too_many_refs : public std::exception
|
||||
{
|
||||
public:
|
||||
virtual const char* what() const throw()
|
||||
{
|
||||
return "too many refs";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(8) // Packing is useful if Count is short or byte
|
||||
template<class T, typename Count = int, Count maxRefs = 0>
|
||||
class AyaBaseClass quick_intrusive_ptr_target
|
||||
{
|
||||
private:
|
||||
Aya::atomic<Count> refs;
|
||||
|
||||
public:
|
||||
inline quick_intrusive_ptr_target()
|
||||
{
|
||||
refs = 0;
|
||||
}
|
||||
friend void boost::intrusive_ptr_add_ref<>(const quick_intrusive_ptr_target<T, Count, maxRefs>* p);
|
||||
friend void boost::intrusive_ptr_release<>(const quick_intrusive_ptr_target<T, Count, maxRefs>* p);
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
template<class T, typename Count = int, Count maxStrong = 0, Count maxWeak = maxStrong>
|
||||
class AyaBaseClass intrusive_ptr_target
|
||||
{
|
||||
private:
|
||||
// The "counts" struct is placed in memory at the head of the object
|
||||
#pragma pack(push)
|
||||
#pragma pack(8) // Packing is useful if Count is short or byte
|
||||
struct counts
|
||||
{
|
||||
Aya::atomic<Count> strong; // #shared
|
||||
Aya::atomic<Count> weak; // #weak + (#shared != 0)
|
||||
counts()
|
||||
{
|
||||
strong = 0;
|
||||
weak = 1;
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static inline counts* fetch(const T* t)
|
||||
{
|
||||
return reinterpret_cast<counts*>((char*)t - sizeof(counts));
|
||||
}
|
||||
|
||||
public:
|
||||
void* operator new(std::size_t t)
|
||||
{
|
||||
void* c = ::malloc(sizeof(counts) + t);
|
||||
|
||||
// placement new the counts:
|
||||
new (c) counts();
|
||||
|
||||
return (char*)c + sizeof(counts);
|
||||
}
|
||||
|
||||
void operator delete(void* p)
|
||||
{
|
||||
counts* c = fetch(reinterpret_cast<T*>(p));
|
||||
// operator delete should only be called if this object
|
||||
// never got touched by the intrusive_ptr functions
|
||||
AYAASSERT(c->strong == 0);
|
||||
AYAASSERT(c->weak == 1);
|
||||
::free(c);
|
||||
}
|
||||
|
||||
friend void boost::intrusive_ptr_add_ref<>(const intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
friend void boost::intrusive_ptr_release<>(const intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
friend void boost::intrusive_ptr_add_weak_ref<>(const intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
friend bool boost::intrusive_ptr_expired<>(const intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
friend bool boost::intrusive_ptr_try_lock<>(const intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
friend void boost::intrusive_ptr_weak_release<>(const intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p);
|
||||
};
|
||||
} // namespace Aya
|
||||
|
||||
|
||||
namespace boost
|
||||
{
|
||||
/// Template specialization quick_intrusive_ptr_target
|
||||
template<class T, typename Count, Count maxRefs>
|
||||
void intrusive_ptr_add_ref(const Aya::quick_intrusive_ptr_target<T, Count, maxRefs>* p)
|
||||
{
|
||||
if (maxRefs > 0 && p->refs >= maxRefs)
|
||||
throw Aya::too_many_refs();
|
||||
|
||||
const_cast<Aya::quick_intrusive_ptr_target<T, Count, maxRefs>*>(p)->refs++;
|
||||
}
|
||||
|
||||
template<class T, typename Count, Count maxRefs>
|
||||
void intrusive_ptr_release(const Aya::quick_intrusive_ptr_target<T, Count, maxRefs>* p)
|
||||
{
|
||||
AYAASSERT(p->refs > 0);
|
||||
if (--(const_cast<Aya::quick_intrusive_ptr_target<T, Count, maxRefs>*>(p)->refs) == 0)
|
||||
delete static_cast<const T*>(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Template specialization intrusive_ptr_target
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_add_ref(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p)
|
||||
{
|
||||
typename Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::counts* c =
|
||||
Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::fetch(static_cast<const T*>(p));
|
||||
|
||||
if (maxStrong > 0)
|
||||
{
|
||||
if (++(c->strong) > maxStrong)
|
||||
throw Aya::too_many_refs();
|
||||
}
|
||||
else
|
||||
{
|
||||
c->strong++;
|
||||
AYAASSERT(c->strong < std::numeric_limits<Count>::max() - 10);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_release(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p)
|
||||
{
|
||||
const T* t = static_cast<const T*>(p);
|
||||
typedef typename Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::counts Counts;
|
||||
Counts* c = Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::fetch(t);
|
||||
|
||||
if (--(c->strong) == 0)
|
||||
{
|
||||
// placement delete the object, but not the counts
|
||||
t->~T();
|
||||
|
||||
if (--(c->weak) == 0)
|
||||
{
|
||||
// placement delete the counts and reclaim composite object memory
|
||||
c->Counts::~counts();
|
||||
::free((void*)c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_add_weak_ref(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p)
|
||||
{
|
||||
typename Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::counts* c =
|
||||
Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::fetch(static_cast<const T*>(p));
|
||||
|
||||
AYAASSERT(c->strong > 0);
|
||||
|
||||
if (maxWeak > 0)
|
||||
{
|
||||
if (++(c->weak) > maxWeak + 1) // weak already has a ref because of the strong refs
|
||||
throw Aya::too_many_refs();
|
||||
}
|
||||
else
|
||||
{
|
||||
++(c->weak);
|
||||
AYAASSERT(c->weak < std::numeric_limits<Count>::max() - 10);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
bool intrusive_ptr_expired(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p)
|
||||
{
|
||||
typename Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::counts* c =
|
||||
Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::fetch(static_cast<const T*>(p));
|
||||
return c->strong == 0;
|
||||
}
|
||||
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
bool intrusive_ptr_try_lock(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p)
|
||||
{
|
||||
typename Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::counts* c =
|
||||
Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::fetch(static_cast<const T*>(p));
|
||||
|
||||
while (true)
|
||||
{
|
||||
Count tmp = c->strong;
|
||||
if (tmp == 0)
|
||||
return false;
|
||||
if (c->strong.compare_and_swap(tmp + 1, tmp) == tmp)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, typename Count, Count maxStrong, Count maxWeak>
|
||||
void intrusive_ptr_weak_release(const Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>* p)
|
||||
{
|
||||
const T* t = static_cast<const T*>(p);
|
||||
typedef typename Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::counts Counts;
|
||||
Counts* c = Aya::intrusive_ptr_target<T, Count, maxStrong, maxWeak>::fetch(t);
|
||||
|
||||
if (--(c->weak) == 0)
|
||||
{
|
||||
AYAASSERT(c->strong == 0);
|
||||
// placement delete the counts and reclaim composite object memory
|
||||
c->Counts::~counts();
|
||||
|
||||
::free((void*)c);
|
||||
}
|
||||
}
|
||||
} // namespace boost
|
||||
268
engine/core/src/intrusive_set.hpp
Normal file
268
engine/core/src/intrusive_set.hpp
Normal file
@@ -0,0 +1,268 @@
|
||||
#pragma once
|
||||
|
||||
#include "Debug.hpp"
|
||||
#include "boost/noncopyable.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
namespace Intrusive
|
||||
{
|
||||
|
||||
// A very efficient, constant time, unordered set
|
||||
// Features:
|
||||
// Constant-time insert
|
||||
// Constant-time remove
|
||||
// Constant-time membership test
|
||||
// items can remove themselves
|
||||
// Items in the set auto-remove themselves upon destruction
|
||||
// no memory is allocated for operations
|
||||
// everything is nothrow
|
||||
//
|
||||
// TODO: Adding an item to a set silently removes it for membership in another set. Is this desirable? Should it be a runtime error?
|
||||
// TODO: Implement ConstIterator
|
||||
|
||||
template<class Item, class Tag = Item>
|
||||
class Set : boost::noncopyable
|
||||
{
|
||||
private:
|
||||
class NextRef
|
||||
{
|
||||
friend class Set;
|
||||
|
||||
protected:
|
||||
Item* next;
|
||||
inline NextRef() throw()
|
||||
: next(0)
|
||||
{
|
||||
}
|
||||
inline NextRef(const NextRef& other) throw()
|
||||
: next(0)
|
||||
{
|
||||
// Copies of objects aren't automatically added to containers
|
||||
}
|
||||
inline NextRef& operator=(const NextRef& other) throw()
|
||||
{
|
||||
// This object retains its membership to its container
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
class Hook : public NextRef
|
||||
{
|
||||
friend class Set;
|
||||
Set* _container;
|
||||
NextRef* prev;
|
||||
|
||||
public:
|
||||
inline Hook() throw()
|
||||
: _container(0)
|
||||
, prev(0)
|
||||
{
|
||||
}
|
||||
inline Hook(const Hook& other) throw()
|
||||
: _container(0)
|
||||
, prev(0)
|
||||
{
|
||||
// Copies of objects aren't automatically added to containers
|
||||
}
|
||||
inline Hook& operator=(const Hook& other) throw()
|
||||
{
|
||||
// This object retains its membership to its container
|
||||
}
|
||||
inline ~Hook() throw()
|
||||
{
|
||||
remove();
|
||||
}
|
||||
inline void remove() throw()
|
||||
{
|
||||
if (is_linked())
|
||||
{
|
||||
AYAASSERT(prev != 0 || Set::NextRef::next != 0);
|
||||
|
||||
if (prev)
|
||||
prev->Set::NextRef::next = NextRef::next;
|
||||
if (NextRef::next)
|
||||
NextRef::next->Set::Hook::prev = prev;
|
||||
|
||||
_container->count--;
|
||||
|
||||
NextRef::next = 0;
|
||||
prev = 0;
|
||||
_container = 0;
|
||||
}
|
||||
}
|
||||
inline bool is_linked() const throw()
|
||||
{
|
||||
AYAASSERT((_container != 0) == ((Set::NextRef::next != 0) || (prev != 0)));
|
||||
return _container != 0;
|
||||
}
|
||||
inline Set* container() throw()
|
||||
{
|
||||
return _container;
|
||||
}
|
||||
};
|
||||
|
||||
class Iterator
|
||||
{
|
||||
friend class Set;
|
||||
Item* item;
|
||||
Iterator(Item* item) throw()
|
||||
: item(item)
|
||||
{
|
||||
AYAASSERT(!item || item->Set::Hook::is_linked());
|
||||
}
|
||||
|
||||
public:
|
||||
inline Iterator() throw()
|
||||
: item(0)
|
||||
{
|
||||
}
|
||||
|
||||
inline bool operator==(const Iterator& other) const throw()
|
||||
{
|
||||
return item == other.item;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Iterator& other) const throw()
|
||||
{
|
||||
return item != other.item;
|
||||
}
|
||||
|
||||
inline Item* operator->() throw()
|
||||
{
|
||||
AYAASSERT(item);
|
||||
AYAASSERT(!item || item->Set::Hook::is_linked());
|
||||
return item;
|
||||
}
|
||||
|
||||
inline Item& operator*() throw()
|
||||
{
|
||||
AYAASSERT(item);
|
||||
AYAASSERT(!item || item->Set::Hook::is_linked());
|
||||
return *item;
|
||||
}
|
||||
|
||||
inline Iterator& operator++() throw()
|
||||
{
|
||||
AYAASSERT(item);
|
||||
|
||||
item = item->Set::Hook::next;
|
||||
|
||||
AYAASSERT(!item || item->Set::Hook::is_linked());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool empty() const throw()
|
||||
{
|
||||
return item == 0;
|
||||
}
|
||||
|
||||
// for std iterators:
|
||||
typedef std::forward_iterator_tag iterator_category;
|
||||
typedef Item& value_type;
|
||||
typedef void difference_type;
|
||||
typedef /*typename*/ Item* pointer;
|
||||
typedef /*typename*/ Item& reference;
|
||||
};
|
||||
|
||||
inline Set() throw()
|
||||
: count(0)
|
||||
{
|
||||
}
|
||||
|
||||
inline ~Set() throw()
|
||||
{
|
||||
for (Iterator iter = begin(); !iter.empty(); iter = erase(iter))
|
||||
;
|
||||
}
|
||||
|
||||
inline size_t size() const throw()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
inline bool empty() const throw()
|
||||
{
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
Iterator erase(Iterator iter) throw()
|
||||
{
|
||||
Item& item(*iter);
|
||||
++iter;
|
||||
remove_element(item);
|
||||
return iter;
|
||||
}
|
||||
|
||||
bool remove_element(Item& item) throw()
|
||||
{
|
||||
if (item.Set::Hook::_container == this)
|
||||
{
|
||||
item.Set::Hook::remove();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void insert(Item& item) throw()
|
||||
{
|
||||
if (item.Set::Hook::_container == this)
|
||||
return;
|
||||
|
||||
// Items can be in only one list at a time
|
||||
item.Set::Hook::remove();
|
||||
|
||||
AYAASSERT(!item.Set::Hook::next);
|
||||
AYAASSERT(!item.Set::Hook::prev);
|
||||
|
||||
Item* head = head_ref.next;
|
||||
if (head)
|
||||
{
|
||||
AYAASSERT(head->Set::Hook::is_linked());
|
||||
AYAASSERT(head->Set::Hook::container() == this);
|
||||
item.Set::NextRef::next = head;
|
||||
head->Set::Hook::prev = &item;
|
||||
}
|
||||
head_ref.next = &item;
|
||||
item.Set::Hook::prev = &head_ref;
|
||||
|
||||
item.Set::Hook::_container = this;
|
||||
AYAASSERT(item.Set::Hook::next || item.Set::Hook::prev);
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
inline Iterator begin() throw()
|
||||
{
|
||||
return Iterator(head_ref.next);
|
||||
}
|
||||
|
||||
inline Iterator end() throw()
|
||||
{
|
||||
return Iterator();
|
||||
}
|
||||
|
||||
// For the std iterator pattern:
|
||||
typedef Iterator iterator;
|
||||
|
||||
// For the boost::intrusive pattern:
|
||||
inline void push_front(Item& item) throw()
|
||||
{
|
||||
insert(item);
|
||||
}
|
||||
|
||||
// For the boost::intrusive pattern:
|
||||
inline Iterator iterator_to(Item& item) throw()
|
||||
{
|
||||
return item.Set::Hook::_container == this ? Iterator(&item) : Iterator();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t count;
|
||||
NextRef head_ref;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Intrusive
|
||||
} // namespace Aya
|
||||
138
engine/core/src/intrusive_weak_ptr.hpp
Normal file
138
engine/core/src/intrusive_weak_ptr.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include "boost/intrusive_ptr.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
/*
|
||||
An extension to boost::intrusive_ptr.
|
||||
To use this class, the target class needs to implement
|
||||
some functions in addition to those required by boost::instrusive_ptr:
|
||||
|
||||
bool intrusive_ptr_expired(const T* p);
|
||||
bool intrusive_ptr_try_lock(const T* p);
|
||||
void intrusive_ptr_add_weak_ref(const T* p);
|
||||
void intrusive_ptr_weak_release(const T* p);
|
||||
|
||||
Note: Aya::intrusive_ptr_target is a nice wrapper class that implements
|
||||
all required functions
|
||||
|
||||
*/
|
||||
template<class T>
|
||||
class intrusive_weak_ptr
|
||||
{
|
||||
T* p_;
|
||||
|
||||
public:
|
||||
inline intrusive_weak_ptr()
|
||||
: p_(0)
|
||||
{
|
||||
}
|
||||
|
||||
inline intrusive_weak_ptr(T* p)
|
||||
: p_(p)
|
||||
{
|
||||
if (p_ != 0)
|
||||
boost::intrusive_ptr_add_weak_ref(p_);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
inline intrusive_weak_ptr(const intrusive_weak_ptr<U>& rhs)
|
||||
: p_(0)
|
||||
{
|
||||
if (!rhs.expired())
|
||||
{
|
||||
p_ = rhs.raw();
|
||||
boost::intrusive_ptr_add_weak_ref(p_);
|
||||
}
|
||||
}
|
||||
|
||||
inline intrusive_weak_ptr(const intrusive_weak_ptr& rhs)
|
||||
: p_(0)
|
||||
{
|
||||
if (!rhs.expired())
|
||||
{
|
||||
p_ = rhs.raw();
|
||||
boost::intrusive_ptr_add_weak_ref(p_);
|
||||
}
|
||||
}
|
||||
|
||||
template<class U>
|
||||
inline intrusive_weak_ptr(const boost::intrusive_ptr<U>& rhs)
|
||||
: p_(rhs.get())
|
||||
{
|
||||
if (p_ != 0)
|
||||
boost::intrusive_ptr_add_weak_ref(p_);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
inline intrusive_weak_ptr& operator=(T* p)
|
||||
{
|
||||
reset(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class U>
|
||||
inline intrusive_weak_ptr& operator=(const boost::intrusive_ptr<U>& rhs)
|
||||
{
|
||||
reset(rhs.get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline intrusive_weak_ptr& operator=(const Aya::intrusive_weak_ptr<T>& rhs)
|
||||
{
|
||||
reset(rhs.raw());
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class U>
|
||||
inline intrusive_weak_ptr& operator=(const Aya::intrusive_weak_ptr<U>& rhs)
|
||||
{
|
||||
reset(rhs.raw());
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ~intrusive_weak_ptr()
|
||||
{
|
||||
if (p_ != 0)
|
||||
boost::intrusive_ptr_weak_release(p_);
|
||||
}
|
||||
|
||||
inline void reset()
|
||||
{
|
||||
if (p_ != 0)
|
||||
{
|
||||
boost::intrusive_ptr_weak_release(p_);
|
||||
p_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void reset(T* p)
|
||||
{
|
||||
if (p_ != 0)
|
||||
boost::intrusive_ptr_weak_release(p_);
|
||||
p_ = p;
|
||||
if (p_ != 0)
|
||||
boost::intrusive_ptr_add_weak_ref(p_);
|
||||
}
|
||||
|
||||
inline boost::intrusive_ptr<T> lock() const
|
||||
{
|
||||
if (p_ && boost::intrusive_ptr_try_lock(p_))
|
||||
return boost::intrusive_ptr<T>(p_, false);
|
||||
else
|
||||
return boost::intrusive_ptr<T>();
|
||||
}
|
||||
|
||||
inline bool expired() const
|
||||
{
|
||||
return (!p_ || boost::intrusive_ptr_expired(p_));
|
||||
}
|
||||
|
||||
// TODO: Can we hide this?
|
||||
inline T* raw() const
|
||||
{
|
||||
return p_;
|
||||
}
|
||||
};
|
||||
} // namespace Aya
|
||||
175
engine/core/src/make_shared.hpp
Normal file
175
engine/core/src/make_shared.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/type_traits/type_with_alignment.hpp>
|
||||
#include <boost/type_traits/alignment_of.hpp>
|
||||
#include <cstddef>
|
||||
#include <new>
|
||||
|
||||
// This is a copy of make_shared from boost 1.42.1
|
||||
// When we upgrade we can use boost directly
|
||||
// NOTE: 1.38.1 has an undocumented make_shared.hpp, but I don't know if it is
|
||||
// safe or not to use.
|
||||
namespace Aya
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template<std::size_t N, std::size_t A>
|
||||
struct sp_aligned_storage
|
||||
{
|
||||
union type
|
||||
{
|
||||
char data_[N];
|
||||
typename boost::type_with_alignment<A>::type align_;
|
||||
};
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class sp_ms_deleter
|
||||
{
|
||||
private:
|
||||
typedef typename sp_aligned_storage<sizeof(T), ::boost::alignment_of<T>::value>::type storage_type;
|
||||
|
||||
bool initialized_;
|
||||
storage_type storage_;
|
||||
|
||||
private:
|
||||
void destroy()
|
||||
{
|
||||
if (initialized_)
|
||||
{
|
||||
reinterpret_cast<T*>(storage_.data_)->~T();
|
||||
initialized_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
sp_ms_deleter()
|
||||
: initialized_(false)
|
||||
{
|
||||
}
|
||||
|
||||
// optimization: do not copy storage_
|
||||
sp_ms_deleter(sp_ms_deleter const&)
|
||||
: initialized_(false)
|
||||
{
|
||||
}
|
||||
|
||||
~sp_ms_deleter()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
void operator()(T*)
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
void* address()
|
||||
{
|
||||
return storage_.data_;
|
||||
}
|
||||
|
||||
void set_initialized()
|
||||
{
|
||||
initialized_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(BOOST_HAS_RVALUE_REFS)
|
||||
template<class T>
|
||||
T&& sp_forward(T& t)
|
||||
{
|
||||
return static_cast<T&&>(t);
|
||||
}
|
||||
#endif
|
||||
} // namespace detail
|
||||
|
||||
// TODO: This implementation may not support shared_from_this properly. Upgrade to new boost, which implements this for us
|
||||
template<class T>
|
||||
boost::shared_ptr<T> make_shared()
|
||||
{
|
||||
boost::shared_ptr<T> pt(static_cast<T*>(0), Aya::detail::sp_ms_deleter<T>());
|
||||
|
||||
Aya::detail::sp_ms_deleter<T>* pd = boost::get_deleter<Aya::detail::sp_ms_deleter<T>>(pt);
|
||||
|
||||
void* pv = pd->address();
|
||||
|
||||
::new (pv) T();
|
||||
pd->set_initialized();
|
||||
|
||||
T* pt2 = static_cast<T*>(pv);
|
||||
|
||||
return boost::shared_ptr<T>(pt, pt2);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
boost::shared_ptr<T> make_shared(std::allocator<T> a)
|
||||
{
|
||||
boost::shared_ptr<T> pt(static_cast<T*>(0), Aya::detail::sp_ms_deleter<T>(), a);
|
||||
|
||||
Aya::detail::sp_ms_deleter<T>* pd = boost::get_deleter<Aya::detail::sp_ms_deleter<T>>(pt);
|
||||
|
||||
void* pv = pd->address();
|
||||
|
||||
::new (pv) T();
|
||||
pd->set_initialized();
|
||||
|
||||
T* pt2 = static_cast<T*>(pv);
|
||||
|
||||
return boost::shared_ptr<T>(pt, pt2);
|
||||
}
|
||||
|
||||
|
||||
template<class T, class A1>
|
||||
boost::shared_ptr<T> make_shared(A1 const& a1)
|
||||
{
|
||||
boost::shared_ptr<T> pt(static_cast<T*>(0), Aya::detail::sp_ms_deleter<T>());
|
||||
|
||||
Aya::detail::sp_ms_deleter<T>* pd = boost::get_deleter<Aya::detail::sp_ms_deleter<T>>(pt);
|
||||
|
||||
void* pv = pd->address();
|
||||
|
||||
::new (pv) T(a1);
|
||||
pd->set_initialized();
|
||||
|
||||
T* pt2 = static_cast<T*>(pv);
|
||||
|
||||
return boost::shared_ptr<T>(pt, pt2);
|
||||
}
|
||||
|
||||
template<class T, class A1, class A2>
|
||||
boost::shared_ptr<T> make_shared(A1 const& a1, A2 const& a2)
|
||||
{
|
||||
boost::shared_ptr<T> pt(static_cast<T*>(0), Aya::detail::sp_ms_deleter<T>());
|
||||
|
||||
Aya::detail::sp_ms_deleter<T>* pd = boost::get_deleter<Aya::detail::sp_ms_deleter<T>>(pt);
|
||||
|
||||
void* pv = pd->address();
|
||||
|
||||
::new (pv) T(a1, a2);
|
||||
pd->set_initialized();
|
||||
|
||||
T* pt2 = static_cast<T*>(pv);
|
||||
|
||||
return boost::shared_ptr<T>(pt, pt2);
|
||||
}
|
||||
|
||||
template<class T, class A1, class A2, class A3>
|
||||
boost::shared_ptr<T> make_shared(A1 const& a1, A2 const& a2, A3 const& a3)
|
||||
{
|
||||
boost::shared_ptr<T> pt(static_cast<T*>(0), Aya::detail::sp_ms_deleter<T>());
|
||||
|
||||
Aya::detail::sp_ms_deleter<T>* pd = boost::get_deleter<Aya::detail::sp_ms_deleter<T>>(pt);
|
||||
|
||||
void* pv = pd->address();
|
||||
|
||||
::new (pv) T(a1, a2, a3);
|
||||
pd->set_initialized();
|
||||
|
||||
T* pt2 = static_cast<T*>(pv);
|
||||
|
||||
return boost::shared_ptr<T>(pt, pt2);
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
6
engine/core/src/microprofile/README.md
Normal file
6
engine/core/src/microprofile/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# microprofile [](https://travis-ci.org/zeux/microprofile)
|
||||
This is a fork of [microprofile by Jonas Meyer](https://bitbucket.org/jonasmeyer/microprofile).
|
||||
|
||||
microprofile is an embeddable CPU/GPU profiler with an in-app and HTML visualizers.
|
||||
|
||||

|
||||
10
engine/core/src/microprofile/dtrace_contextswitch
Normal file
10
engine/core/src/microprofile/dtrace_contextswitch
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
MPPIPE=/tmp/.microprofilecspipe
|
||||
while true;
|
||||
do
|
||||
echo "DTrace run; output file: $MPPIPE"
|
||||
mkfifo $MPPIPE
|
||||
dtrace -q -n fbt::thread_dispatch:return'{printf("MPTD %x %x %x\n", cpu, tid, machtimestamp)}' -o $MPPIPE
|
||||
rm $MPPIPE
|
||||
read -p "DTrace run finished, press Enter to restart..."
|
||||
done
|
||||
4212
engine/core/src/microprofile/microprofile.hpp
Normal file
4212
engine/core/src/microprofile/microprofile.hpp
Normal file
File diff suppressed because it is too large
Load Diff
556
engine/core/src/microprofile/microprofiledraw.hpp
Normal file
556
engine/core/src/microprofile/microprofiledraw.hpp
Normal file
@@ -0,0 +1,556 @@
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
// For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
#ifndef MICROPROFILE_ENABLED
|
||||
#error "microprofile.h must be included before including microprofiledraw.h"
|
||||
#endif
|
||||
|
||||
#ifndef MICROPROFILEDRAW_ENABLED
|
||||
#define MICROPROFILEDRAW_ENABLED MICROPROFILE_ENABLED
|
||||
#endif
|
||||
|
||||
#ifndef MICROPROFILEDRAW_API
|
||||
#define MICROPROFILEDRAW_API
|
||||
#endif
|
||||
|
||||
#if MICROPROFILEDRAW_ENABLED
|
||||
MICROPROFILEDRAW_API void MicroProfileDrawInitGL();
|
||||
|
||||
MICROPROFILEDRAW_API void MicroProfileRender(uint32_t nWidth, uint32_t nHeight, float fScale);
|
||||
|
||||
MICROPROFILEDRAW_API void MicroProfileBeginDraw(uint32_t nWidth, uint32_t nHeight, float* pfProjection);
|
||||
MICROPROFILEDRAW_API void MicroProfileBeginDraw(uint32_t nWidth, uint32_t nHeight, float fScale);
|
||||
MICROPROFILEDRAW_API void MicroProfileEndDraw();
|
||||
|
||||
#ifdef MICROPROFILEDRAW_IMPL
|
||||
struct MicroProfileDrawVertex
|
||||
{
|
||||
float nX;
|
||||
float nY;
|
||||
uint32_t nColor;
|
||||
float fU;
|
||||
float fV;
|
||||
};
|
||||
|
||||
struct MicroProfileDrawCommand
|
||||
{
|
||||
uint32_t nCommand;
|
||||
uint32_t nNumVertices;
|
||||
};
|
||||
|
||||
struct MicroProfileDrawContext
|
||||
{
|
||||
enum
|
||||
{
|
||||
MAX_COMMANDS = 32,
|
||||
MAX_VERTICES = 16384,
|
||||
};
|
||||
|
||||
bool bInitialized;
|
||||
|
||||
GLuint nVAO;
|
||||
GLuint nVertexBuffer;
|
||||
GLuint nProgram;
|
||||
GLuint nTexture;
|
||||
|
||||
int nAttributePosition;
|
||||
int nAttributeColor;
|
||||
int nAttributeTexture;
|
||||
int nUniformProjectionMatrix;
|
||||
|
||||
uint32_t nVertexPos;
|
||||
uint32_t nCommandPos;
|
||||
|
||||
MicroProfileDrawVertex nVertices[MAX_VERTICES];
|
||||
MicroProfileDrawCommand nCommands[MAX_COMMANDS];
|
||||
};
|
||||
|
||||
MicroProfileDrawContext g_MicroProfileDraw;
|
||||
|
||||
#define Q0(d, member, v) d[0].member = v
|
||||
#define Q1(d, member, v) \
|
||||
d[1].member = v; \
|
||||
d[3].member = v
|
||||
#define Q2(d, member, v) d[4].member = v
|
||||
#define Q3(d, member, v) \
|
||||
d[2].member = v; \
|
||||
d[5].member = v
|
||||
|
||||
#define FONT_TEX_X 1024
|
||||
#define FONT_TEX_Y 9
|
||||
#define FONT_SIZE (FONT_TEX_X * FONT_TEX_Y * 4)
|
||||
|
||||
namespace
|
||||
{
|
||||
extern const uint8_t g_MicroProfileFont[];
|
||||
extern const uint16_t g_MicroProfileFontDescription[];
|
||||
|
||||
extern const char g_MicroProfileVertexShader_110[];
|
||||
extern const char g_MicroProfileFragmentShader_110[];
|
||||
|
||||
extern const char g_MicroProfileVertexShader_150[];
|
||||
extern const char g_MicroProfileFragmentShader_150[];
|
||||
} // namespace
|
||||
|
||||
bool MicroProfileCompileShader(GLuint* pnHandle, int nType, const char* pShader)
|
||||
{
|
||||
*pnHandle = glCreateShader(nType);
|
||||
glShaderSource(*pnHandle, 1, &pShader, 0);
|
||||
glCompileShader(*pnHandle);
|
||||
|
||||
GLint compiled = 0;
|
||||
glGetShaderiv(*pnHandle, GL_COMPILE_STATUS, &compiled);
|
||||
|
||||
if (!compiled)
|
||||
{
|
||||
char temp[4096];
|
||||
glGetShaderInfoLog(*pnHandle, 4096, NULL, temp);
|
||||
printf("SHADER FAILED TO COMPILE:\n%s\n", temp);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MicroProfileLinkProgram(GLuint* pnHandle, GLuint nVertexShader, GLuint nFragmentShader)
|
||||
{
|
||||
*pnHandle = glCreateProgram();
|
||||
glAttachShader(*pnHandle, nVertexShader);
|
||||
glAttachShader(*pnHandle, nFragmentShader);
|
||||
glLinkProgram(*pnHandle);
|
||||
|
||||
GLint linked = 0;
|
||||
glGetProgramiv(*pnHandle, GL_LINK_STATUS, &linked);
|
||||
|
||||
if (!linked)
|
||||
{
|
||||
char temp[4096];
|
||||
glGetProgramInfoLog(*pnHandle, 4096, NULL, temp);
|
||||
printf("PROGRAM FAILED TO LINK:\n%s\n", temp);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MicroProfileDrawInitGL()
|
||||
{
|
||||
MicroProfileDrawContext& S = g_MicroProfileDraw;
|
||||
|
||||
MP_ASSERT(!S.bInitialized);
|
||||
|
||||
const GLubyte* pGLVersion = glGetString(GL_VERSION);
|
||||
const GLubyte* pGLSLVersion = glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
|
||||
int nGLVersion = (pGLVersion[0] - '0') * 10 + (pGLVersion[2] - '0');
|
||||
int nGLSLVersion = (pGLSLVersion[0] - '0') * 100 + (pGLSLVersion[2] - '0') * 10 + (pGLSLVersion[3] - '0');
|
||||
|
||||
glGenBuffers(1, &S.nVertexBuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, S.nVertexBuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(S.nVertices), 0, GL_STREAM_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
if (nGLVersion >= 3)
|
||||
glGenVertexArrays(1, &S.nVAO);
|
||||
else
|
||||
S.nVAO = 0;
|
||||
|
||||
GLuint nVertexShader;
|
||||
if (!MicroProfileCompileShader(
|
||||
&nVertexShader, GL_VERTEX_SHADER, nGLSLVersion >= 150 ? g_MicroProfileVertexShader_150 : g_MicroProfileVertexShader_110))
|
||||
return;
|
||||
|
||||
GLuint nFragmentShader;
|
||||
if (!MicroProfileCompileShader(
|
||||
&nFragmentShader, GL_FRAGMENT_SHADER, nGLSLVersion >= 150 ? g_MicroProfileFragmentShader_150 : g_MicroProfileFragmentShader_110))
|
||||
return;
|
||||
|
||||
if (!MicroProfileLinkProgram(&S.nProgram, nVertexShader, nFragmentShader))
|
||||
return;
|
||||
|
||||
S.nAttributePosition = glGetAttribLocation(S.nProgram, "VertexIn");
|
||||
S.nAttributeColor = glGetAttribLocation(S.nProgram, "ColorIn");
|
||||
S.nAttributeTexture = glGetAttribLocation(S.nProgram, "TCIn");
|
||||
|
||||
S.nUniformProjectionMatrix = glGetUniformLocation(S.nProgram, "ProjectionMatrix");
|
||||
|
||||
glUseProgram(S.nProgram);
|
||||
glUniform1i(glGetUniformLocation(S.nProgram, "Texture"), 0);
|
||||
glUniform1f(glGetUniformLocation(S.nProgram, "RcpFontHeight"), 1.f / FONT_TEX_Y);
|
||||
glUseProgram(0);
|
||||
|
||||
uint32_t* pUnpacked = (uint32_t*)alloca(FONT_SIZE);
|
||||
int idx = 0;
|
||||
int end = FONT_TEX_X * FONT_TEX_Y / 8;
|
||||
for (int i = 0; i < end; i++)
|
||||
{
|
||||
unsigned char pValue = g_MicroProfileFont[i];
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
pUnpacked[idx++] = pValue & 0x80 ? (uint32_t)-1 : 0;
|
||||
pValue <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pUnpacked[idx - 1] = 0xffffffff;
|
||||
|
||||
uint32_t* p4 = &pUnpacked[0];
|
||||
glGenTextures(1, &S.nTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, S.nTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FONT_TEX_X, FONT_TEX_Y, 0, GL_RGBA, GL_UNSIGNED_BYTE, &p4[0]);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
S.bInitialized = true;
|
||||
}
|
||||
|
||||
void MicroProfileBeginDraw(uint32_t nWidth, uint32_t nHeight, float* pfProjection)
|
||||
{
|
||||
MicroProfileDrawContext& S = g_MicroProfileDraw;
|
||||
|
||||
if (!S.bInitialized)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
if (S.nVAO)
|
||||
glBindVertexArray(S.nVAO);
|
||||
|
||||
glUseProgram(S.nProgram);
|
||||
glUniformMatrix4fv(S.nUniformProjectionMatrix, 1, 0, pfProjection);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, S.nTexture);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, S.nVertexBuffer);
|
||||
|
||||
int nStride = sizeof(MicroProfileDrawVertex);
|
||||
|
||||
glVertexAttribPointer(S.nAttributePosition, 2, GL_FLOAT, 0, nStride, (void*)(offsetof(MicroProfileDrawVertex, nX)));
|
||||
glVertexAttribPointer(S.nAttributeColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, nStride, (void*)(offsetof(MicroProfileDrawVertex, nColor)));
|
||||
glVertexAttribPointer(S.nAttributeTexture, 2, GL_FLOAT, 0, nStride, (void*)(offsetof(MicroProfileDrawVertex, fU)));
|
||||
|
||||
glEnableVertexAttribArray(S.nAttributePosition);
|
||||
glEnableVertexAttribArray(S.nAttributeColor);
|
||||
glEnableVertexAttribArray(S.nAttributeTexture);
|
||||
|
||||
S.nVertexPos = 0;
|
||||
S.nCommandPos = 0;
|
||||
}
|
||||
|
||||
void MicroProfileBeginDraw(uint32_t nWidth, uint32_t nHeight, float fScale)
|
||||
{
|
||||
MicroProfileDrawContext& S = g_MicroProfileDraw;
|
||||
|
||||
if (!S.bInitialized)
|
||||
return;
|
||||
|
||||
float left = 0.f;
|
||||
float right = nWidth / fScale;
|
||||
float bottom = nHeight / fScale;
|
||||
float top = 0.f;
|
||||
float near_plane = -1.f;
|
||||
float far_plane = 1.f;
|
||||
|
||||
float projection[16] = {};
|
||||
|
||||
projection[0] = 2.0f / (right - left);
|
||||
projection[5] = 2.0f / (top - bottom);
|
||||
projection[10] = -2.0f / (far_plane - near_plane);
|
||||
projection[12] = -(right + left) / (right - left);
|
||||
projection[13] = -(top + bottom) / (top - bottom);
|
||||
projection[14] = -(far_plane + near_plane) / (far_plane - near_plane);
|
||||
projection[15] = 1.f;
|
||||
|
||||
MicroProfileBeginDraw(nWidth, nHeight, projection);
|
||||
}
|
||||
|
||||
void MicroProfileFlush()
|
||||
{
|
||||
MicroProfileDrawContext& S = g_MicroProfileDraw;
|
||||
|
||||
if (S.nVertexPos == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPEI("MicroProfile", "Flush", 0xffff3456);
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, S.nVertexPos * sizeof(MicroProfileDrawVertex), S.nVertices);
|
||||
|
||||
int nOffset = 0;
|
||||
|
||||
for (int i = 0; i < int(S.nCommandPos); ++i)
|
||||
{
|
||||
int nCount = S.nCommands[i].nNumVertices;
|
||||
glDrawArrays(S.nCommands[i].nCommand, nOffset, nCount);
|
||||
nOffset += nCount;
|
||||
}
|
||||
|
||||
S.nVertexPos = 0;
|
||||
S.nCommandPos = 0;
|
||||
}
|
||||
|
||||
MicroProfileDrawVertex* MicroProfilePushVertices(uint32_t nCommand, int nCount)
|
||||
{
|
||||
MP_ASSERT(nCount <= MicroProfileDrawContext::MAX_VERTICES);
|
||||
|
||||
MicroProfileDrawContext& S = g_MicroProfileDraw;
|
||||
|
||||
if (S.nVertexPos + nCount > MicroProfileDrawContext::MAX_VERTICES)
|
||||
MicroProfileFlush();
|
||||
|
||||
if (S.nCommandPos && S.nCommands[S.nCommandPos - 1].nCommand == nCommand)
|
||||
{
|
||||
S.nCommands[S.nCommandPos - 1].nNumVertices += nCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (S.nCommandPos == MicroProfileDrawContext::MAX_COMMANDS)
|
||||
MicroProfileFlush();
|
||||
|
||||
S.nCommands[S.nCommandPos].nCommand = nCommand;
|
||||
S.nCommands[S.nCommandPos].nNumVertices = nCount;
|
||||
S.nCommandPos++;
|
||||
}
|
||||
|
||||
uint32_t nOut = S.nVertexPos;
|
||||
S.nVertexPos += nCount;
|
||||
|
||||
return &S.nVertices[nOut];
|
||||
}
|
||||
|
||||
void MicroProfileEndDraw()
|
||||
{
|
||||
MicroProfileDrawContext& S = g_MicroProfileDraw;
|
||||
|
||||
if (!S.bInitialized)
|
||||
return;
|
||||
|
||||
MicroProfileFlush();
|
||||
|
||||
glDisableVertexAttribArray(S.nAttributePosition);
|
||||
glDisableVertexAttribArray(S.nAttributeColor);
|
||||
glDisableVertexAttribArray(S.nAttributeTexture);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glUseProgram(0);
|
||||
|
||||
if (S.nVAO)
|
||||
glBindVertexArray(0);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void MicroProfileRender(uint32_t nWidth, uint32_t nHeight, float fScale)
|
||||
{
|
||||
MicroProfileBeginDraw(nWidth, nHeight, fScale);
|
||||
MicroProfileDraw(nWidth, nHeight);
|
||||
MicroProfileEndDraw();
|
||||
}
|
||||
|
||||
void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText, uint32_t nLen)
|
||||
{
|
||||
MICROPROFILE_SCOPEI("MicroProfile", "TextDraw", 0xff88ee);
|
||||
|
||||
const float fOffsetU = 5.f / 1024.f;
|
||||
MP_ASSERT(nLen <= strlen(pText));
|
||||
float fX = (float)nX;
|
||||
float fY = (float)nY;
|
||||
float fY2 = fY + (MICROPROFILE_TEXT_HEIGHT + 1);
|
||||
|
||||
MicroProfileDrawVertex* pVertex = MicroProfilePushVertices(GL_TRIANGLES, 6 * nLen);
|
||||
if (!pVertex)
|
||||
return;
|
||||
|
||||
const char* pStr = pText;
|
||||
nColor = 0xff000000 | ((nColor & 0xff) << 16) | (nColor & 0xff00) | ((nColor >> 16) & 0xff);
|
||||
|
||||
for (uint32_t j = 0; j < nLen; ++j)
|
||||
{
|
||||
int16_t nOffset = g_MicroProfileFontDescription[uint8_t(*pStr++)];
|
||||
float fOffset = nOffset / 1024.f;
|
||||
Q0(pVertex, nX, fX);
|
||||
Q0(pVertex, nY, fY);
|
||||
Q0(pVertex, nColor, nColor);
|
||||
Q0(pVertex, fU, fOffset);
|
||||
Q0(pVertex, fV, 0.f);
|
||||
|
||||
Q1(pVertex, nX, fX + MICROPROFILE_TEXT_WIDTH);
|
||||
Q1(pVertex, nY, fY);
|
||||
Q1(pVertex, nColor, nColor);
|
||||
Q1(pVertex, fU, fOffset + fOffsetU);
|
||||
Q1(pVertex, fV, 0.f);
|
||||
|
||||
Q2(pVertex, nX, fX + MICROPROFILE_TEXT_WIDTH);
|
||||
Q2(pVertex, nY, fY2);
|
||||
Q2(pVertex, nColor, nColor);
|
||||
Q2(pVertex, fU, fOffset + fOffsetU);
|
||||
Q2(pVertex, fV, 1.f);
|
||||
|
||||
|
||||
Q3(pVertex, nX, fX);
|
||||
Q3(pVertex, nY, fY2);
|
||||
Q3(pVertex, nColor, nColor);
|
||||
Q3(pVertex, fU, fOffset);
|
||||
Q3(pVertex, fV, 1.f);
|
||||
|
||||
fX += MICROPROFILE_TEXT_WIDTH + 1;
|
||||
pVertex += 6;
|
||||
}
|
||||
}
|
||||
void MicroProfileDrawBox(int nX0, int nY0, int nX1, int nY1, uint32_t nColor, MicroProfileBoxType Type)
|
||||
{
|
||||
MicroProfileDrawVertex* pVertex = MicroProfilePushVertices(GL_TRIANGLES, 6);
|
||||
if (!pVertex)
|
||||
return;
|
||||
|
||||
if (Type == MicroProfileBoxTypeFlat)
|
||||
{
|
||||
nColor = ((nColor & 0xff) << 16) | ((nColor >> 16) & 0xff) | (0xff00ff00 & nColor);
|
||||
Q0(pVertex, nX, (float)nX0);
|
||||
Q0(pVertex, nY, (float)nY0);
|
||||
Q0(pVertex, nColor, nColor);
|
||||
Q0(pVertex, fU, 2.f);
|
||||
Q0(pVertex, fV, 2.f);
|
||||
Q1(pVertex, nX, (float)nX1);
|
||||
Q1(pVertex, nY, (float)nY0);
|
||||
Q1(pVertex, nColor, nColor);
|
||||
Q1(pVertex, fU, 2.f);
|
||||
Q1(pVertex, fV, 2.f);
|
||||
Q2(pVertex, nX, (float)nX1);
|
||||
Q2(pVertex, nY, (float)nY1);
|
||||
Q2(pVertex, nColor, nColor);
|
||||
Q2(pVertex, fU, 2.f);
|
||||
Q2(pVertex, fV, 2.f);
|
||||
Q3(pVertex, nX, (float)nX0);
|
||||
Q3(pVertex, nY, (float)nY1);
|
||||
Q3(pVertex, nColor, nColor);
|
||||
Q3(pVertex, fU, 2.f);
|
||||
Q3(pVertex, fV, 2.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t r = 0xff & (nColor >> 16);
|
||||
uint32_t g = 0xff & (nColor >> 8);
|
||||
uint32_t b = 0xff & nColor;
|
||||
uint32_t nMax = MicroProfileMax(MicroProfileMax(MicroProfileMax(r, g), b), 30u);
|
||||
uint32_t nMin = MicroProfileMin(MicroProfileMin(MicroProfileMin(r, g), b), 180u);
|
||||
|
||||
uint32_t r0 = 0xff & ((r + nMax) / 2);
|
||||
uint32_t g0 = 0xff & ((g + nMax) / 2);
|
||||
uint32_t b0 = 0xff & ((b + nMax) / 2);
|
||||
|
||||
uint32_t r1 = 0xff & ((r + nMin) / 2);
|
||||
uint32_t g1 = 0xff & ((g + nMin) / 2);
|
||||
uint32_t b1 = 0xff & ((b + nMin) / 2);
|
||||
uint32_t nColor0 = (r0 << 0) | (g0 << 8) | (b0 << 16) | (0xff000000 & nColor);
|
||||
uint32_t nColor1 = (r1 << 0) | (g1 << 8) | (b1 << 16) | (0xff000000 & nColor);
|
||||
Q0(pVertex, nX, (float)nX0);
|
||||
Q0(pVertex, nY, (float)nY0);
|
||||
Q0(pVertex, nColor, nColor0);
|
||||
Q0(pVertex, fU, 2.f);
|
||||
Q0(pVertex, fV, 2.f);
|
||||
Q1(pVertex, nX, (float)nX1);
|
||||
Q1(pVertex, nY, (float)nY0);
|
||||
Q1(pVertex, nColor, nColor0);
|
||||
Q1(pVertex, fU, 3.f);
|
||||
Q1(pVertex, fV, 2.f);
|
||||
Q2(pVertex, nX, (float)nX1);
|
||||
Q2(pVertex, nY, (float)nY1);
|
||||
Q2(pVertex, nColor, nColor1);
|
||||
Q2(pVertex, fU, 3.f);
|
||||
Q2(pVertex, fV, 3.f);
|
||||
Q3(pVertex, nX, (float)nX0);
|
||||
Q3(pVertex, nY, (float)nY1);
|
||||
Q3(pVertex, nColor, nColor1);
|
||||
Q3(pVertex, fU, 2.f);
|
||||
Q3(pVertex, fV, 3.f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MicroProfileDrawLine2D(uint32_t nVertices, float* pVertices, uint32_t nColor)
|
||||
{
|
||||
if (!nVertices)
|
||||
return;
|
||||
|
||||
MicroProfileDrawVertex* pVertex = MicroProfilePushVertices(GL_LINES, 2 * (nVertices - 1));
|
||||
nColor = 0xff000000 | ((nColor & 0xff) << 16) | (nColor & 0xff00ff00) | ((nColor >> 16) & 0xff);
|
||||
for (uint32_t i = 0; i < nVertices - 1; ++i)
|
||||
{
|
||||
pVertex[0].nX = pVertices[i * 2];
|
||||
pVertex[0].nY = pVertices[i * 2 + 1];
|
||||
pVertex[0].nColor = nColor;
|
||||
pVertex[0].fU = 2.f;
|
||||
pVertex[0].fV = 2.f;
|
||||
pVertex[1].nX = pVertices[(i + 1) * 2];
|
||||
pVertex[1].nY = pVertices[(i + 1) * 2 + 1];
|
||||
pVertex[1].nColor = nColor;
|
||||
pVertex[1].fU = 2.f;
|
||||
pVertex[1].fV = 2.f;
|
||||
pVertex += 2;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
const char g_MicroProfileVertexShader_110[] = "#version 110\n\
|
||||
uniform mat4 ProjectionMatrix; \
|
||||
attribute vec3 VertexIn; attribute vec4 ColorIn; attribute vec2 TCIn; \
|
||||
varying vec2 TC; varying vec4 Color; \
|
||||
void main() { Color = ColorIn; TC = TCIn; gl_Position = ProjectionMatrix * vec4(VertexIn, 1.0); }";
|
||||
|
||||
const char g_MicroProfileVertexShader_150[] = "#version 150\n\
|
||||
uniform mat4 ProjectionMatrix; \
|
||||
in vec3 VertexIn; in vec4 ColorIn; in vec2 TCIn; \
|
||||
out vec2 TC; out vec4 Color; \
|
||||
void main() { Color = ColorIn; TC = TCIn; gl_Position = ProjectionMatrix * vec4(VertexIn, 1.0); }";
|
||||
|
||||
const char g_MicroProfileFragmentShader_110[] = "#version 110\n\
|
||||
uniform sampler2D Texture; uniform float RcpFontHeight; \
|
||||
varying vec2 TC; varying vec4 Color; \
|
||||
void main() { \
|
||||
vec4 c0 = texture2D(Texture, TC.xy); \
|
||||
vec4 c1 = texture2D(Texture, TC.xy + vec2(0.0, RcpFontHeight)); \
|
||||
gl_FragColor = c0.w < 0.5 ? vec4(0, 0, 0, c1.w) : c0 * Color; \
|
||||
} \
|
||||
";
|
||||
|
||||
const char g_MicroProfileFragmentShader_150[] = "#version 150\n\
|
||||
uniform sampler2D Texture; uniform float RcpFontHeight; \
|
||||
in vec2 TC; in vec4 Color; \
|
||||
out vec4 result; \
|
||||
void main() { \
|
||||
vec4 c0 = texture(Texture, TC.xy); \
|
||||
vec4 c1 = texture(Texture, TC.xy + vec2(0.0, RcpFontHeight)); \
|
||||
result = c0.w < 0.5 ? vec4(0, 0, 0, c1.w) : c0 * Color; \
|
||||
} \
|
||||
";
|
||||
|
||||
#include "microprofilefont.hpp"
|
||||
} // namespace
|
||||
#endif
|
||||
#endif
|
||||
1418
engine/core/src/microprofile/microprofilefont.hpp
Normal file
1418
engine/core/src/microprofile/microprofilefont.hpp
Normal file
File diff suppressed because it is too large
Load Diff
4047
engine/core/src/microprofile/microprofilehtml.hpp
Normal file
4047
engine/core/src/microprofile/microprofilehtml.hpp
Normal file
File diff suppressed because it is too large
Load Diff
3757
engine/core/src/microprofile/microprofileui.hpp
Normal file
3757
engine/core/src/microprofile/microprofileui.hpp
Normal file
File diff suppressed because it is too large
Load Diff
90
engine/core/src/object_pool.hpp
Normal file
90
engine/core/src/object_pool.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "boost/pool/object_pool.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
// T must have a non-throwing destructor
|
||||
template<typename T, typename UserAllocator>
|
||||
class object_pool : public boost::object_pool<T, UserAllocator>
|
||||
{
|
||||
protected:
|
||||
struct CallDestructor
|
||||
{
|
||||
void operator()(T* item)
|
||||
{
|
||||
item->~T();
|
||||
}
|
||||
} callDestructor;
|
||||
|
||||
public:
|
||||
#ifndef _WIN32
|
||||
// gcc can't access typdef from base class...
|
||||
typedef typename boost::pool<UserAllocator>::size_type size_type;
|
||||
#endif
|
||||
|
||||
// This constructor parameter is an extension!
|
||||
explicit object_pool<T, UserAllocator>(const size_type next_size = 32)
|
||||
: boost::object_pool<T, UserAllocator>(next_size)
|
||||
{
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void for_each(F& f)
|
||||
{
|
||||
// handle trivial case
|
||||
if (!this->list.valid())
|
||||
return;
|
||||
|
||||
boost::details::PODptr<size_type> iter = this->list;
|
||||
boost::details::PODptr<size_type> next = iter;
|
||||
|
||||
// Start 'freed_iter' at beginning of free list
|
||||
void* freed_iter = this->first;
|
||||
|
||||
const size_type partition_size = this->alloc_size();
|
||||
|
||||
do
|
||||
{
|
||||
// increment next
|
||||
next = next.next();
|
||||
|
||||
// delete all contained objects that aren't freed
|
||||
|
||||
// Iterate 'i' through all chunks in the memory block
|
||||
for (char* i = iter.begin(); i != iter.end(); i += partition_size)
|
||||
{
|
||||
// If this chunk is free
|
||||
if (i == freed_iter)
|
||||
{
|
||||
// Increment freed_iter to point to next in free list
|
||||
freed_iter = boost::simple_segregated_storage<size_type>::nextof(freed_iter);
|
||||
|
||||
// Continue searching chunks in the memory block
|
||||
continue;
|
||||
}
|
||||
|
||||
// This chunk is not free (allocated), so call f
|
||||
f(static_cast<T*>(static_cast<void*>(i)));
|
||||
// and continue searching chunks in the memory block
|
||||
}
|
||||
|
||||
// increment iter
|
||||
iter = next;
|
||||
} while (iter.valid());
|
||||
}
|
||||
|
||||
// go through all objects, calling desctructors.
|
||||
// then use store's purge operation (which doesn't call destructors)
|
||||
void clear()
|
||||
{
|
||||
for_each(callDestructor);
|
||||
|
||||
boost::pool<UserAllocator>::purge_memory();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
48
engine/core/src/signal.cpp
Normal file
48
engine/core/src/signal.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "signal.hpp"
|
||||
|
||||
LOGVARIABLE(ScopedConnection, 0);
|
||||
|
||||
struct Init
|
||||
{
|
||||
static void initStaticData() {}
|
||||
Init()
|
||||
{
|
||||
static boost::once_flag flag = BOOST_ONCE_INIT;
|
||||
boost::call_once(&initStaticData, flag);
|
||||
}
|
||||
};
|
||||
static Init init;
|
||||
|
||||
using namespace Aya;
|
||||
using namespace signals;
|
||||
|
||||
boost::function<void(std::exception&)> Aya::signals::slot_exception_handler(0);
|
||||
|
||||
void connection::disconnect() const
|
||||
{
|
||||
boost::intrusive_ptr<islot> s(weak_slot.lock());
|
||||
if (s)
|
||||
s->disconnect();
|
||||
}
|
||||
|
||||
bool connection::connected() const
|
||||
{
|
||||
boost::intrusive_ptr<islot> s(weak_slot.lock());
|
||||
return s && s->connected();
|
||||
}
|
||||
|
||||
bool connection::operator==(const connection& other) const
|
||||
{
|
||||
return weak_slot.lock() == other.weak_slot.lock();
|
||||
}
|
||||
|
||||
bool connection::operator!=(const connection& other) const
|
||||
{
|
||||
return weak_slot.lock() != other.weak_slot.lock();
|
||||
}
|
||||
|
||||
connection& connection::operator=(const connection& con)
|
||||
{
|
||||
weak_slot = con.weak_slot;
|
||||
return *this;
|
||||
}
|
||||
774
engine/core/src/signal.hpp
Normal file
774
engine/core/src/signal.hpp
Normal file
@@ -0,0 +1,774 @@
|
||||
|
||||
#pragma once
|
||||
#include "boost.hpp"
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
#include "Memory.hpp"
|
||||
#include "intrusive_weak_ptr.hpp"
|
||||
#include "boost/any.hpp"
|
||||
#include "boost/type_traits.hpp"
|
||||
#include "threadsafe.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include <limits>
|
||||
#include "callable.hpp"
|
||||
|
||||
// aya - instead of intrusive_ptr, use shared_ptr on unix
|
||||
|
||||
using boost::shared_ptr;
|
||||
using boost::weak_ptr;
|
||||
|
||||
LOGGROUP(ScopedConnection);
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define AYA_SIGNALS_DEBUGGING
|
||||
#endif
|
||||
|
||||
#ifdef AYA_SIGNALS_DEBUGGING
|
||||
#define AYA_SIGNALS_ASSERT AYA_CRASH_ASSERT
|
||||
#pragma optimize("", off)
|
||||
#else
|
||||
#define AYA_SIGNALS_ASSERT AYAASSERT
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// The classes in this namespace mimic a small fraction of the features contained
|
||||
// in boost signals
|
||||
namespace signals
|
||||
{
|
||||
/*
|
||||
This signal class is similar to boost::signal, but with a few
|
||||
important differences. First, it doesn't implement nearly as
|
||||
much functionality as boost's. It merely implements those
|
||||
portions that Roblox uses.
|
||||
The big advantage to this implementation is its (limited) thread
|
||||
safety. Any thread is allowed to connect new slots to this
|
||||
signal and also disconnect them at any time. The firing of a signal
|
||||
is not thread safe - only one thread is allowed to fire at a time.
|
||||
*/
|
||||
|
||||
// Set this to whatever you want to handle exceptions thrown by a slot
|
||||
extern boost::function<void(std::exception&)> slot_exception_handler;
|
||||
|
||||
class connection
|
||||
{
|
||||
public:
|
||||
class islot
|
||||
: boost::noncopyable
|
||||
#if 0
|
||||
// No need to test maxStrong, since all strong references are internal
|
||||
// However, weak references are external via connection object. It is
|
||||
// expected that the total weak references to a slot are much less than 64000
|
||||
// NOTE: unsigned short is slightly slower than int on Win32. However, it
|
||||
// saves 4 bytes.
|
||||
, public Aya::intrusive_ptr_target<islot, unsigned short, 0, 0>
|
||||
#else
|
||||
, public Aya::intrusive_ptr_target<islot>
|
||||
#endif
|
||||
{
|
||||
protected:
|
||||
islot() {}
|
||||
|
||||
public:
|
||||
virtual ~islot() {}
|
||||
virtual void disconnect() = 0;
|
||||
virtual bool connected() const = 0;
|
||||
};
|
||||
|
||||
inline connection(islot* slot)
|
||||
: weak_slot(slot)
|
||||
{
|
||||
}
|
||||
inline connection(const connection& con)
|
||||
: weak_slot(con.weak_slot)
|
||||
{
|
||||
}
|
||||
inline connection() {}
|
||||
connection& operator=(const connection& con);
|
||||
|
||||
void disconnect() const;
|
||||
bool connected() const;
|
||||
bool operator==(const connection& other) const;
|
||||
bool operator!=(const connection& other) const;
|
||||
|
||||
void flogPrint()
|
||||
{
|
||||
boost::intrusive_ptr<islot> s(weak_slot.lock());
|
||||
FASTLOG2(FLog::Always, "Connection %p, slot %p", this, s.get());
|
||||
}
|
||||
|
||||
private:
|
||||
// to make connections copyable, the data for a connection are shared
|
||||
Aya::intrusive_weak_ptr<islot> weak_slot; // must be weak to avoid memory leaks
|
||||
};
|
||||
|
||||
class scoped_connection : boost::noncopyable
|
||||
{
|
||||
// Has-a instead of Is-a. We do this because demoting scoped_connection reference
|
||||
// to a connection will alter the meaning of the = operator, leading to strange
|
||||
// bugs.
|
||||
connection con;
|
||||
|
||||
public:
|
||||
inline scoped_connection() {}
|
||||
inline scoped_connection(const connection& con)
|
||||
: con(con)
|
||||
{
|
||||
}
|
||||
|
||||
inline scoped_connection& operator=(const connection& con)
|
||||
{
|
||||
if (this->con != con)
|
||||
{
|
||||
disconnect();
|
||||
this->con = con;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
inline ~scoped_connection()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
// Accessor to underlying connection, if you really want it
|
||||
inline connection& get()
|
||||
{
|
||||
return con;
|
||||
}
|
||||
|
||||
// implementation of connection contract
|
||||
inline void disconnect() const
|
||||
{
|
||||
con.disconnect();
|
||||
}
|
||||
inline bool connected() const
|
||||
{
|
||||
return con.connected();
|
||||
}
|
||||
inline bool operator==(const connection& other) const
|
||||
{
|
||||
return con == other;
|
||||
}
|
||||
inline bool operator!=(const connection& other) const
|
||||
{
|
||||
return con != other;
|
||||
}
|
||||
};
|
||||
|
||||
class scoped_connection_logged : boost::noncopyable
|
||||
{
|
||||
// Has-a instead of Is-a. We do this because demoting scoped_connection reference
|
||||
// to a connection will alter the meaning of the = operator, leading to strange
|
||||
// bugs.
|
||||
connection con;
|
||||
bool logged;
|
||||
|
||||
public:
|
||||
inline scoped_connection_logged()
|
||||
: logged(false)
|
||||
{
|
||||
}
|
||||
inline scoped_connection_logged(bool logged)
|
||||
: logged(logged)
|
||||
{
|
||||
}
|
||||
|
||||
// Helper for using FastLog groups as trigger
|
||||
inline scoped_connection_logged(FLog::Channel channelId)
|
||||
: logged(channelId != 0)
|
||||
{
|
||||
}
|
||||
|
||||
inline scoped_connection_logged(const connection& con)
|
||||
: con(con)
|
||||
{
|
||||
}
|
||||
|
||||
inline scoped_connection_logged& operator=(const connection& con)
|
||||
{
|
||||
if (this->con != con)
|
||||
{
|
||||
disconnect();
|
||||
this->con = con;
|
||||
if (logged)
|
||||
{
|
||||
FASTLOG2(FLog::Always, "Scoped connection %p assign: %p", this, &con);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
inline ~scoped_connection_logged()
|
||||
{
|
||||
if (logged)
|
||||
FASTLOG1(FLog::Always, "Scoped connection %p destructor", this);
|
||||
disconnect();
|
||||
}
|
||||
|
||||
// Accessor to underlying connection, if you really want it
|
||||
inline connection& get()
|
||||
{
|
||||
return con;
|
||||
}
|
||||
|
||||
inline void setLogged(bool logged)
|
||||
{
|
||||
this->logged = logged;
|
||||
}
|
||||
|
||||
// implementation of connection contract
|
||||
inline void disconnect() const
|
||||
{
|
||||
if (logged)
|
||||
FASTLOG2(FLog::Always, "Scoped connection %p disconnect, previously connected: %u", this, con.connected());
|
||||
con.disconnect();
|
||||
}
|
||||
inline bool connected() const
|
||||
{
|
||||
return con.connected();
|
||||
}
|
||||
inline bool operator==(const connection& other) const
|
||||
{
|
||||
return con == other;
|
||||
}
|
||||
inline bool operator!=(const connection& other) const
|
||||
{
|
||||
return con != other;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal : boost::noncopyable
|
||||
{
|
||||
protected:
|
||||
friend class slot;
|
||||
class slot
|
||||
: public connection::islot
|
||||
, public icallable<boost::function_traits<Signature>::arity, Signature>
|
||||
{
|
||||
public:
|
||||
boost::intrusive_ptr<slot> next;
|
||||
|
||||
signal* sig;
|
||||
|
||||
inline slot(signal* sig)
|
||||
: sig(sig)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool connected() const
|
||||
{
|
||||
return sig != NULL;
|
||||
}
|
||||
|
||||
public:
|
||||
SAFE_HEAP_STATIC(boost::mutex, mutex);
|
||||
|
||||
virtual void disconnect()
|
||||
{
|
||||
if (!sig)
|
||||
return;
|
||||
|
||||
boost::mutex::scoped_lock lock(mutex());
|
||||
|
||||
if (sig)
|
||||
{
|
||||
signal* s = sig;
|
||||
sig = NULL;
|
||||
s->remove(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<class Delegate>
|
||||
class callable_slot : public callable<slot, Delegate, boost::function_traits<Signature>::arity, Signature>
|
||||
{
|
||||
public:
|
||||
inline callable_slot(const Delegate& deleg, signal* sig)
|
||||
: callable<slot, Delegate, boost::function_traits<Signature>::arity, Signature>(deleg, sig)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
// The slots are stored in a linked list, with "head" as a dummy slot used to anchor the list.
|
||||
|
||||
boost::intrusive_ptr<slot> head;
|
||||
|
||||
// TODO: Avoid contention by using one mutex per signal? Or an array of signals?
|
||||
// TODO: Is boost::mutex the best choice? Does it start up with a spin?
|
||||
// However, at least we have a separate mutex for each signature
|
||||
|
||||
// SAFE_HEAP_STATIC is used instead of SAFE_STATIC to work around global variables using signals (like GameSettings)
|
||||
// If you use SAFE_STATIC, mutex can be destroyed before other global variables using signals,
|
||||
// so signal destructor will fail on mutex access
|
||||
SAFE_HEAP_STATIC(boost::mutex, mutex)
|
||||
|
||||
void remove(slot* item)
|
||||
{
|
||||
// Invariant: the value of item->next does not change
|
||||
AYAASSERT(!boost::intrusive_ptr_expired(item));
|
||||
|
||||
if (item == head)
|
||||
head = item->next;
|
||||
else
|
||||
{
|
||||
// Find "prev". This is O(n)
|
||||
slot* prev = head.get();
|
||||
// TODO: Can we just assert that prev!=NULL?
|
||||
while (prev && prev->next != item)
|
||||
prev = prev->next.get();
|
||||
|
||||
// In theory prev should never be NULL, because for it to be NULL
|
||||
// the slot would be destroyed, in which case remove() can't be
|
||||
// called. Let's play it safe and null-check anyway.
|
||||
AYA_SIGNALS_ASSERT(!prev || prev->next.get() == item);
|
||||
|
||||
if (prev)
|
||||
prev->next = item->next;
|
||||
}
|
||||
|
||||
AYAASSERT(!boost::intrusive_ptr_expired(item));
|
||||
|
||||
// item is now deletable
|
||||
}
|
||||
|
||||
void insert(slot* item)
|
||||
{
|
||||
AYA_SIGNALS_ASSERT(item);
|
||||
|
||||
boost::mutex::scoped_lock lock(mutex());
|
||||
|
||||
if (!head)
|
||||
{
|
||||
head = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
item->next = head;
|
||||
head = item;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
inline signal()
|
||||
{
|
||||
mutex();
|
||||
}
|
||||
|
||||
inline ~signal()
|
||||
{
|
||||
disconnectAll();
|
||||
}
|
||||
|
||||
void disconnectAll()
|
||||
{
|
||||
while (head)
|
||||
{
|
||||
boost::intrusive_ptr<slot> node;
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mutex());
|
||||
|
||||
// See DE131 for a justification of this "chunk" code
|
||||
const int chunkSize = 10;
|
||||
int count = chunkSize;
|
||||
for (node = head; node; node = node->next)
|
||||
{
|
||||
node->sig = NULL;
|
||||
if (count-- == 0)
|
||||
{
|
||||
// After 10 iterations we need to break out and collect
|
||||
// the slots. Otherwise we risk a stack crash
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the next line will cause nodes to be destroyed.
|
||||
// Notice that we want them to be destroyed
|
||||
// outside of the mutex lock because
|
||||
// destruction could have side-effects.
|
||||
head = node;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool empty() const
|
||||
{
|
||||
return !head;
|
||||
}
|
||||
|
||||
template<class Delegate>
|
||||
connection connect(const Delegate& function)
|
||||
{
|
||||
slot* item = new callable_slot<Delegate>(function, this);
|
||||
insert(item);
|
||||
return connection(item);
|
||||
}
|
||||
|
||||
// For debugging:
|
||||
static size_t sizeof_slot()
|
||||
{
|
||||
return sizeof(slot);
|
||||
}
|
||||
|
||||
void flogPrint()
|
||||
{
|
||||
printf("Signal - %p\n", this);
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
while (this->next(item))
|
||||
printf("Signal slot = %p\n", item.get());
|
||||
// FASTLOG1(FLog::Always, "Signal slot = %p", item.get());
|
||||
}
|
||||
|
||||
protected:
|
||||
void on_error(std::exception& e)
|
||||
{
|
||||
if (slot_exception_handler)
|
||||
slot_exception_handler(e);
|
||||
}
|
||||
|
||||
bool next(boost::intrusive_ptr<slot>& item)
|
||||
{
|
||||
if (!item)
|
||||
{
|
||||
// Start iterating; this is safe to read from
|
||||
// If another thread is in the process of prepending, we can get old head or new head
|
||||
// If we do get the new head it should already have the new next so this is race-free
|
||||
item = this->head;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Advance the iterator; we keep item alive so next is safe to read from
|
||||
// If another thread is in the process of removing the 'item->next' connection we may see
|
||||
// the next pointer either pointing to the element that's being removed or to the next one
|
||||
// Since replacing next is atomic and we can't observe any other values than these two this is
|
||||
// also race-free.
|
||||
item = item->next;
|
||||
}
|
||||
|
||||
if (!item)
|
||||
{
|
||||
// Done iterating
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Iteration succeeded
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<int arity, typename Signature>
|
||||
class signal_with_args;
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<0, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call();
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()()
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
typedef typename Aya::signals::signal<Signature>::slot slot;
|
||||
boost::intrusive_ptr<slot> item;
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get());
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<1, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item, typename boost::function_traits<Signature>::arg1_type arg1)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call(arg1);
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()(typename boost::function_traits<Signature>::arg1_type arg1)
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get(), arg1);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<2, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item, typename boost::function_traits<Signature>::arg1_type arg1,
|
||||
typename boost::function_traits<Signature>::arg2_type arg2)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call(arg1, arg2);
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2)
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get(), arg1, arg2);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<3, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item, typename boost::function_traits<Signature>::arg1_type arg1,
|
||||
typename boost::function_traits<Signature>::arg2_type arg2, typename boost::function_traits<Signature>::arg3_type arg3)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call(arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3)
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get(), arg1, arg2, arg3);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<4, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item, typename boost::function_traits<Signature>::arg1_type arg1,
|
||||
typename boost::function_traits<Signature>::arg2_type arg2, typename boost::function_traits<Signature>::arg3_type arg3,
|
||||
typename boost::function_traits<Signature>::arg4_type arg4)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call(arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4)
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get(), arg1, arg2, arg3, arg4);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<5, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item, typename boost::function_traits<Signature>::arg1_type arg1,
|
||||
typename boost::function_traits<Signature>::arg2_type arg2, typename boost::function_traits<Signature>::arg3_type arg3,
|
||||
typename boost::function_traits<Signature>::arg4_type arg4, typename boost::function_traits<Signature>::arg5_type arg5)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call(arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5)
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get(), arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<6, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item, typename boost::function_traits<Signature>::arg1_type arg1,
|
||||
typename boost::function_traits<Signature>::arg2_type arg2, typename boost::function_traits<Signature>::arg3_type arg3,
|
||||
typename boost::function_traits<Signature>::arg4_type arg4, typename boost::function_traits<Signature>::arg5_type arg5,
|
||||
typename boost::function_traits<Signature>::arg6_type arg6)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call(arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5, typename boost::function_traits<Signature>::arg6_type arg6)
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get(), arg1, arg2, arg3, arg4, arg5, arg6);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature>
|
||||
class signal_with_args<7, Signature> : public signal<Signature>
|
||||
{
|
||||
static inline void fireItem(typename signal<Signature>::slot* item, typename boost::function_traits<Signature>::arg1_type arg1,
|
||||
typename boost::function_traits<Signature>::arg2_type arg2, typename boost::function_traits<Signature>::arg3_type arg3,
|
||||
typename boost::function_traits<Signature>::arg4_type arg4, typename boost::function_traits<Signature>::arg5_type arg5,
|
||||
typename boost::function_traits<Signature>::arg6_type arg6, typename boost::function_traits<Signature>::arg7_type arg7)
|
||||
{
|
||||
if (item->sig) // Make sure this guy hasn't been disconnected
|
||||
item->call(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||
}
|
||||
|
||||
public:
|
||||
void operator()(typename boost::function_traits<Signature>::arg1_type arg1, typename boost::function_traits<Signature>::arg2_type arg2,
|
||||
typename boost::function_traits<Signature>::arg3_type arg3, typename boost::function_traits<Signature>::arg4_type arg4,
|
||||
typename boost::function_traits<Signature>::arg5_type arg5, typename boost::function_traits<Signature>::arg6_type arg6,
|
||||
typename boost::function_traits<Signature>::arg7_type arg7)
|
||||
{
|
||||
if (this->empty())
|
||||
return;
|
||||
|
||||
boost::intrusive_ptr<typename Aya::signals::signal<Signature>::slot> item;
|
||||
begin:
|
||||
try
|
||||
{
|
||||
while (this->next(item))
|
||||
fireItem(item.get(), arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
Aya::signals::signal<Signature>::on_error(e);
|
||||
// Note: We put this handler on the outside of the for loop
|
||||
// as an optimization. This is why we have a goto statement.
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace signals
|
||||
|
||||
template<typename Signature>
|
||||
class signal : public signals::signal_with_args<boost::function_traits<Signature>::arity, Signature>
|
||||
{
|
||||
};
|
||||
|
||||
// Note that remote signal is *not* virtualized against signal. This only works because the Event class is templatized, and not using polymorphism.
|
||||
// If Event becomes polymorphics, THIS CODE WILL FAIL
|
||||
template<typename Signature>
|
||||
class remote_signal : public signal<Signature>
|
||||
{
|
||||
private:
|
||||
typedef signal<Signature> Super;
|
||||
|
||||
public:
|
||||
signal<void()> connectionSignal;
|
||||
|
||||
remote_signal() {}
|
||||
|
||||
template<typename F>
|
||||
signals::connection connect(const F& function)
|
||||
{
|
||||
connectionSignal();
|
||||
return Super::connect(function);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
#ifdef AYA_SIGNALS_DEBUGGING
|
||||
#pragma optimize("", on)
|
||||
#endif
|
||||
542
engine/core/src/simd/simd.hpp
Normal file
542
engine/core/src/simd/simd.hpp
Normal file
@@ -0,0 +1,542 @@
|
||||
#pragma once
|
||||
|
||||
// Include this file only if you need the SIMD functionality (which is a large include).
|
||||
// If you only need the SIMD types (v4f, v4u, v4i), include simd_types.h.
|
||||
|
||||
#include "simd/simd_types.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include "boost/static_assert.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace simd
|
||||
{
|
||||
|
||||
//
|
||||
// Vector Loading
|
||||
//
|
||||
|
||||
// Load 4 elements from memory into a vector
|
||||
// The address must be aligned to 16 byte boundary
|
||||
// r = {s[0],s[1],s[2],s[3]}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> load(const ScalarType* s);
|
||||
|
||||
// Load 3 elements from memory into a vector
|
||||
// The address doesn't need to be aligned
|
||||
// The 4th element doesn't need to be readable.
|
||||
// Only implemented for floats
|
||||
// r = {s[0],s[1],s[2],undefined}
|
||||
AYA_SIMD_INLINE v4f load3(const float* s);
|
||||
|
||||
// Load 4 elements from memory into a vector
|
||||
// The address must be aligned to 4 byte boundary only
|
||||
// r = {s[0],s[1],s[2],s[3]}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> loadUnaligned(const ScalarType* s);
|
||||
|
||||
// Load a single element from memory and duplicate it into all components
|
||||
// r = {s[0],s[0],s[0],s[0]}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> loadSplat(const ScalarType* s);
|
||||
|
||||
// Load a single element from memory and place it in the first component
|
||||
// r = {s[0],*,*,*}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> loadSingle(const ScalarType* s);
|
||||
|
||||
//
|
||||
// Storing
|
||||
//
|
||||
|
||||
// Store the 4 components of the vector into memory
|
||||
// The destination address must be 16 bytes aligned
|
||||
// dst[i] = r[i] for i = 0,1,2,3
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE void store(ScalarType* dst, const v4<ScalarType>& v);
|
||||
|
||||
// Store the 4 components of the vector into memory
|
||||
// The destination address must be 4 bytes aligned only
|
||||
// dst[i] = r[i] for i = 0,1,2,3
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE void storeUnaligned(ScalarType* dst, const v4<ScalarType>& v);
|
||||
|
||||
// Store the single x components of the vector into memory
|
||||
// The destination address must be 4 bytes aligned only
|
||||
// *dst = r[0]
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE void storeSingle(ScalarType* dst, const v4<ScalarType>& v);
|
||||
|
||||
//
|
||||
// Vector Forming
|
||||
//
|
||||
|
||||
// Form a vector of floating point 0s
|
||||
// r = {0.0f,0.0f,0.0f,0.0f}
|
||||
AYA_SIMD_INLINE v4f zerof();
|
||||
|
||||
// Duplicate the scalar into all 4 components
|
||||
// r = {s,s,s,s}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> splat(ScalarType s);
|
||||
|
||||
// Form a vector out of 4 scalars
|
||||
// r = {x,y,z,w}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> form(ScalarType x, ScalarType y, ScalarType z, ScalarType w);
|
||||
|
||||
// Form a vector out of 3 scalars
|
||||
// r = {x,y,z,undetermined}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> form(ScalarType x, ScalarType y, ScalarType z);
|
||||
|
||||
// Form a vector out of 2 scalars
|
||||
// r = {x,y,undetermined,undetermined}
|
||||
template<class ScalarType>
|
||||
AYA_SIMD_INLINE v4<ScalarType> form(ScalarType x, ScalarType y);
|
||||
|
||||
//
|
||||
// Insert / extract
|
||||
//
|
||||
|
||||
// Extract the i'th component of the vector
|
||||
// This a very slow operation
|
||||
// r = v[i]
|
||||
template<class VectorType>
|
||||
AYA_SIMD_INLINE typename VectorType::elem_t extractSlow(const VectorType& v, uint32_t i);
|
||||
|
||||
//
|
||||
// Casts: cast one type to the other preserving the bit representation
|
||||
//
|
||||
|
||||
// Cast from float to unsigned
|
||||
// r = 'reinterpret_cast< v4u >'( v )
|
||||
AYA_SIMD_INLINE v4u reinterpretAsUInt(v4fArg v);
|
||||
|
||||
// Cast from float to int
|
||||
// r = 'reinterpret_cast< v4u >'( v )
|
||||
AYA_SIMD_INLINE v4i reinterpretAsInt(v4fArg v);
|
||||
|
||||
// Cast from unsigned to float
|
||||
// r = 'reinterpret_cast< v4f >'( v )
|
||||
AYA_SIMD_INLINE v4f reinterpretAsFloat(v4uArg v);
|
||||
|
||||
// Cast from int to float
|
||||
// r = 'reinterpret_cast< v4f >'( v )
|
||||
AYA_SIMD_INLINE v4f reinterpretAsFloat(v4iArg v);
|
||||
|
||||
// Cast from unsigned to int
|
||||
// r = 'reinterpret_cast< v4i >'( v )
|
||||
AYA_SIMD_INLINE v4i reinterpretAsInt(v4uArg v);
|
||||
|
||||
// Cast from int to unsigned
|
||||
// r = 'reinterpret_cast< v4u >'( v )
|
||||
AYA_SIMD_INLINE v4u reinterpretAsUInt(v4iArg v);
|
||||
|
||||
//
|
||||
// Conversions
|
||||
//
|
||||
|
||||
// Convert float to int rounding to the nearest
|
||||
// r[i] = int( roundToNearest( v[i] ) )
|
||||
AYA_SIMD_INLINE v4i convertFloat2IntNearest(v4fArg v);
|
||||
|
||||
// Convert float to int truncating towards 0
|
||||
// r[i] = int( truncate( v[i] ) )
|
||||
AYA_SIMD_INLINE v4i convertFloat2IntTruncate(v4fArg v);
|
||||
|
||||
// Convert int to float
|
||||
// r[i] = float( v[i] )
|
||||
AYA_SIMD_INLINE v4f convertIntToFloat(v4iArg v);
|
||||
|
||||
//
|
||||
// Selects
|
||||
//
|
||||
|
||||
// Returns a select mask:
|
||||
// r[0] = ( a == 1 ) ? 0xffffffff : 0
|
||||
// r[1] = ( b == 1 ) ? 0xffffffff : 0
|
||||
// r[2] = ( c == 1 ) ? 0xffffffff : 0
|
||||
// r[3] = ( d == 1 ) ? 0xffffffff : 0
|
||||
template<uint32_t a, uint32_t b, uint32_t c, uint32_t d>
|
||||
AYA_SIMD_INLINE v4u selectMask();
|
||||
|
||||
// Select elements between a and b
|
||||
// r[i] = ( mask[i] == 0 ) ? a[i] : ( mask[i] == 0xffffffff ) ? b[i] : undefined
|
||||
template<class VectorType>
|
||||
AYA_SIMD_INLINE VectorType select(const VectorType& a, const VectorType& b, const v4u& mask);
|
||||
|
||||
// r[0] = ( a == 1 ) ? u[0] : v[0]
|
||||
// r[1] = ( b == 1 ) ? u[1] : v[1]
|
||||
// r[2] = ( c == 1 ) ? u[2] : v[2]
|
||||
// r[3] = ( d == 1 ) ? u[3] : v[3]
|
||||
template<uint32_t a, uint32_t b, uint32_t c, uint32_t d, class VectorType>
|
||||
AYA_SIMD_INLINE VectorType select(const VectorType& u, const VectorType& v);
|
||||
|
||||
// Replace an element with an element of the second vector
|
||||
// r = a
|
||||
// r[index] = b[index]
|
||||
template<uint32_t index, class VectorType>
|
||||
AYA_SIMD_INLINE VectorType replace(const VectorType& a, const VectorType& b);
|
||||
|
||||
//
|
||||
// Permutes
|
||||
//
|
||||
|
||||
// r = { a[0], b[0], a[1], b[1] }
|
||||
template<class VectorType>
|
||||
AYA_SIMD_INLINE VectorType zipLow(const VectorType& u, const VectorType& v);
|
||||
|
||||
// r = { a[2], b[2], a[3], b[3] }
|
||||
template<class VectorType>
|
||||
AYA_SIMD_INLINE VectorType zipHigh(const VectorType& u, const VectorType& v);
|
||||
|
||||
// r0 = { a[0], b[0], a[1], b[1] }
|
||||
// r1 = { a[2], b[2], a[3], b[3] }
|
||||
template<class VectorType>
|
||||
AYA_SIMD_INLINE void zip(VectorType& r0, VectorType& r1, const VectorType& u, const VectorType& v);
|
||||
|
||||
// r = { v[2], v[3], u[2], u[3] }
|
||||
template<class VectorType>
|
||||
AYA_SIMD_INLINE VectorType moveHighLow(const VectorType& u, const VectorType& v);
|
||||
|
||||
// r = { u[0], u[1], v[0], v[1] }
|
||||
template<class VectorType>
|
||||
AYA_SIMD_INLINE VectorType moveLowHigh(const VectorType& u, const VectorType& v);
|
||||
|
||||
// Shuffle two vectors
|
||||
// r = { u[a], u[b], v[c], v[d] }
|
||||
template<uint32_t a, uint32_t b, uint32_t c, uint32_t d, class VectorType>
|
||||
AYA_SIMD_INLINE VectorType shuffle(const VectorType& u, const VectorType& v);
|
||||
|
||||
// Duplicate single component of a vector into all components of the result
|
||||
// r = {v[i],v[i],v[i],v[i]}
|
||||
template<unsigned i, class VectorType>
|
||||
AYA_SIMD_INLINE VectorType splat(const VectorType& v);
|
||||
|
||||
// Permute the components of the vector with a compile time permutation
|
||||
// r = {v[a],v[b],v[c],v[d]}
|
||||
template<uint32_t a, uint32_t b, uint32_t c, uint32_t d, class VectorType>
|
||||
AYA_SIMD_INLINE VectorType permute(const VectorType& v);
|
||||
|
||||
// Rotate the vector left by /offset/ elements
|
||||
// r = {v[(offset%4)], v[((offset+1)%4)], v[((offset+2)%4)], v[((offset+3)%4)]}
|
||||
template<uint32_t offset, class VectorType>
|
||||
AYA_SIMD_INLINE VectorType rotateLeft(VectorType& v);
|
||||
|
||||
//
|
||||
// Compares
|
||||
//
|
||||
|
||||
// Return a mask resulting from the compare
|
||||
// r[i] = a[i] > b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u compareGreater(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] > b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator>(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] >= b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u compareGreaterEqual(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] >= b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator>=(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] < b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u compareLess(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] < b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator<(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] <= b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u compareLessEqual(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] <= b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator<=(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] == b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u compare(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] == b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator==(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] != b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator!=(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] == b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u compare(v4iArg a, v4iArg b);
|
||||
|
||||
// r[i] = a[i] == b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator==(v4iArg a, v4iArg b);
|
||||
|
||||
// r[i] = a[i] != b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator!=(v4iArg a, v4iArg b);
|
||||
|
||||
// r[i] = a[i] == b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u compare(v4uArg a, v4uArg b);
|
||||
|
||||
// r[i] = a[i] == b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator==(v4uArg a, v4uArg b);
|
||||
|
||||
// r[i] = a[i] != b[i] ? 0xffffffff : 0
|
||||
AYA_SIMD_INLINE v4u operator!=(v4uArg a, v4uArg b);
|
||||
|
||||
//
|
||||
// Set element to 0
|
||||
//
|
||||
|
||||
// r = v; r[a] = 0;
|
||||
template<uint32_t a>
|
||||
AYA_SIMD_INLINE v4f replaceWithZero(v4fArg v);
|
||||
|
||||
// r = v; r[a] = 0;
|
||||
template<uint32_t a>
|
||||
AYA_SIMD_INLINE v4i replaceWithZero(v4iArg v);
|
||||
|
||||
// r = v; r[a] = 0;
|
||||
template<uint32_t a>
|
||||
AYA_SIMD_INLINE v4u replaceWithZero(v4uArg v);
|
||||
|
||||
//
|
||||
// Float Arithmetics
|
||||
//
|
||||
|
||||
// r[i] = a[i] + b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f operator+(v4fArg a, v4fArg b);
|
||||
|
||||
// a[i] = a[i] + b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f& operator+=(v4f& a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] - b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f operator-(v4fArg a, v4fArg b);
|
||||
|
||||
// a[i] = a[i] - b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f& operator-=(v4f& a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] * b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f operator*(v4fArg a, v4fArg b);
|
||||
|
||||
// a[i] = a[i] * b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f& operator*=(v4f& a, v4fArg b);
|
||||
|
||||
// r[i] = a[i] + b[i] * c[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f mulAdd(v4fArg a, v4fArg b, v4fArg c);
|
||||
|
||||
// r[i] = -a[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f operator-(v4fArg a);
|
||||
|
||||
// r[i] = a[i] / b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f operator/(v4fArg a, v4fArg b);
|
||||
|
||||
// a[i] = a[i] / b[i], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f& operator/=(v4f& a, v4fArg b);
|
||||
|
||||
// r[i] = fabsf( a[i] ), i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f abs(v4fArg a);
|
||||
|
||||
// r[i] = max(a[i], b[i])
|
||||
AYA_SIMD_INLINE v4f max(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = min(a[i], b[i])
|
||||
AYA_SIMD_INLINE v4f min(v4fArg a, v4fArg b);
|
||||
|
||||
//
|
||||
// Estimates
|
||||
//
|
||||
|
||||
// Estimate of the inverse
|
||||
// About 12 bits of precision
|
||||
AYA_SIMD_INLINE v4f inverseEstimate0(v4fArg a);
|
||||
|
||||
// This is like the normal version except for small inputs ( abs(a) < smallestInvertible() ) and large inputs
|
||||
// ( abs(a) > largestInvertible() ) are not handled and will return an undefined number
|
||||
AYA_SIMD_INLINE v4f inverseEstimate0Fast(v4fArg a);
|
||||
|
||||
// More precise version of the inverse estimate
|
||||
AYA_SIMD_INLINE v4f inverseEstimate1(v4fArg a);
|
||||
|
||||
// This is like the normal version except for small inputs ( abs(a) < smallestInvertible() ) and large inputs
|
||||
// ( abs(a) > largestInvertible() ) are not handled and will return an undefined number
|
||||
AYA_SIMD_INLINE v4f inverseEstimate1Fast(v4fArg a);
|
||||
|
||||
// Returns the smallest floating point number r such that inverseEstimate1(r) < inf
|
||||
// Before inverting, you can create a select mask: sel = r < smallestInvertible()
|
||||
AYA_SIMD_INLINE v4f smallestInvertible();
|
||||
|
||||
// Returns the largest floating point number r such that inverseEstimate1(r) > 0.0f
|
||||
// For inputs that are larger than this, the inverse will return 0.0f
|
||||
AYA_SIMD_INLINE v4f largestInvertible();
|
||||
|
||||
// Return an estimate of the inverse square root.
|
||||
// About 12 bits of precision
|
||||
AYA_SIMD_INLINE v4f inverseSqrtEstimate0(v4fArg a);
|
||||
|
||||
// This is like the normal version except for small inputs ( abs(a) < smallestSqrtInvertible() ) and large inputs
|
||||
// ( abs(a) > largestSqrtInvertible() ) are not handled and will return an undefined number
|
||||
AYA_SIMD_INLINE v4f inverseSqrtEstimate0Fast(v4fArg a);
|
||||
|
||||
// Return an estimate of the inverse square root.
|
||||
// More than 12 bits of precision
|
||||
AYA_SIMD_INLINE v4f inverseSqrtEstimate1(v4fArg a);
|
||||
|
||||
// This is like the normal version except for small inputs ( abs(a) < smallestSqrtInvertible() ) and large inputs
|
||||
// ( abs(a) > largestSqrtInvertible() ) are not handled and will return an undefined number
|
||||
AYA_SIMD_INLINE v4f inverseSqrtEstimate1Fast(v4fArg a);
|
||||
|
||||
AYA_SIMD_INLINE v4f smallestSqrtInvertible();
|
||||
|
||||
AYA_SIMD_INLINE v4f largestSqrtInvertible();
|
||||
|
||||
//
|
||||
// Inter-element arithmetic ops
|
||||
//
|
||||
|
||||
// r[i] = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f dotProduct(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[0]*b[0] + a[1]*b[1] + a[2]*b[2], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f dotProduct3(v4fArg a, v4fArg b);
|
||||
|
||||
// r[i] = a[0] + a[1] + a[2] + a[3], i = 0,1,2,3
|
||||
AYA_SIMD_INLINE v4f sumAcross(v4fArg a);
|
||||
|
||||
// r[0] = a[0] + a[1]
|
||||
// r[1] = b[0] + b[1]
|
||||
// r[2] = undefined
|
||||
// r[3] = undefined
|
||||
AYA_SIMD_INLINE v4f sumAcross2(v4fArg a, v4fArg b);
|
||||
|
||||
// r[0] = a[0] + a[1] + a[2]
|
||||
// r[1] = b[0] + b[1] + b[2]
|
||||
// r[2] = undefined
|
||||
// r[3] = undefined
|
||||
AYA_SIMD_INLINE v4f sumAcross3(v4fArg a, v4fArg b);
|
||||
|
||||
// r[0] = a[0] + a[1] + a[2] + a[3]
|
||||
// r[1] = b[0] + b[1] + b[2] + b[3]
|
||||
// r[2] = undefined
|
||||
// r[3] = undefined
|
||||
AYA_SIMD_INLINE v4f sumAcross4(v4fArg a, v4fArg b);
|
||||
|
||||
// r[0] = a[0] + a[1]
|
||||
// r[1] = b[0] + b[1]
|
||||
// r[2] = c[0] + c[1]
|
||||
// r[3] = undefined
|
||||
AYA_SIMD_INLINE v4f sumAcross2(v4fArg a, v4fArg b, v4fArg c);
|
||||
|
||||
// r[0] = a[0] + a[1] + a[2]
|
||||
// r[1] = b[0] + b[1] + b[2]
|
||||
// r[3] = c[0] + c[1] + c[2]
|
||||
// r[3] = undefined
|
||||
AYA_SIMD_INLINE v4f sumAcross3(v4fArg a, v4fArg b, v4fArg c);
|
||||
|
||||
// r[0] = a[0] + a[1] + a[2] + a[3]
|
||||
// r[1] = b[0] + b[1] + b[2] + b[3]
|
||||
// r[3] = c[0] + c[1] + c[2] + c[3]
|
||||
// r[3] = undefined
|
||||
AYA_SIMD_INLINE v4f sumAcross4(v4fArg a, v4fArg b, v4fArg c);
|
||||
|
||||
// r[0] = a[0] + a[1]
|
||||
// r[1] = b[0] + b[1]
|
||||
// r[3] = c[0] + c[1]
|
||||
// r[3] = d[0] + d[1]
|
||||
AYA_SIMD_INLINE v4f sumAcross2(v4fArg a, v4fArg b, v4fArg c, v4fArg d);
|
||||
|
||||
// r[0] = a[0] + a[1] + a[2]
|
||||
// r[1] = b[0] + b[1] + b[2]
|
||||
// r[3] = c[0] + c[1] + c[2]
|
||||
// r[3] = d[0] + d[1] + d[2]
|
||||
AYA_SIMD_INLINE v4f sumAcross3(v4fArg a, v4fArg b, v4fArg c, v4fArg d);
|
||||
|
||||
// r[0] = a[0] + a[1] + a[2] + a[3]
|
||||
// r[1] = b[0] + b[1] + b[2] + b[3]
|
||||
// r[3] = c[0] + c[1] + c[2] + c[3]
|
||||
// r[3] = d[0] + d[1] + d[2] + d[3]
|
||||
AYA_SIMD_INLINE v4f sumAcross4(v4fArg a, v4fArg b, v4fArg c, v4fArg d);
|
||||
|
||||
//
|
||||
// Packing / unpacking
|
||||
//
|
||||
|
||||
// Pack 4 vector-3's into 3 vector-4's
|
||||
// p[0] = {a[0],a[1],a[2],b[0]}
|
||||
// p[1] = {b[1],b[2],c[0],c[1]}
|
||||
// p[2] = {c[2],d[0],d[1],d[2]}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void pack3(typename T::pod_t* __restrict p, const T& a, const T& b, const T& c, const T& d);
|
||||
|
||||
// Pack 3 vector-4's into 4 vector-3's
|
||||
// a = {p[0][0],p[0][1],p[0][2],*}
|
||||
// b = {p[0][3],p[1][0],p[1][1],*}
|
||||
// c = {p[1][2],p[1][3],p[2][0],*}
|
||||
// d = {p[2][1],p[2][2],p[2][3],*}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void unpack3(T& a, T& b, T& c, T& d, const typename T::pod_t* p);
|
||||
|
||||
//
|
||||
// Matrix permutations
|
||||
//
|
||||
|
||||
// Gather the X components of a,b,c,d:
|
||||
// r = {a[0],b[0],c[0],d[0]}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE T gatherX(const T& a, const T& b, const T& c, const T& d);
|
||||
|
||||
// Gather the X components of a,b,c:
|
||||
// r = {a[0],b[0],c[0],undetermined}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE T gatherX(const T& a, const T& b, const T& c);
|
||||
|
||||
// Gather the X components of a,b:
|
||||
// r = {a[0],b[0],undetermined,undetermined}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE T gatherX(const T& a, const T& b);
|
||||
|
||||
// Transpose a 4x4 matrix
|
||||
// a = {x[0],y[0],z[0],w[0]}
|
||||
// b = {x[1],y[1],z[1],w[1]}
|
||||
// c = {x[2],y[2],z[2],w[2]}
|
||||
// d = {x[3],y[3],z[3],w[3]}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose(T& a, T& b, T& c, T& d, const T& x, const T& y, const T& z, const T& w);
|
||||
|
||||
// Transpose a 3x4 matrix
|
||||
// a = {x[0],y[0],z[0],undetermined}
|
||||
// b = {x[1],y[1],z[1],undetermined}
|
||||
// c = {x[2],y[2],z[2],undetermined}
|
||||
// d = {x[3],y[3],z[3],undetermined}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose3x4(T& a, T& b, T& c, T& d, const T& x, const T& y, const T& z);
|
||||
|
||||
// Transpose a 4x3 matrix
|
||||
// x = {a[0],b[0],c[0],d[0]}
|
||||
// y = {a[1],b[1],c[1],d[1]}
|
||||
// z = {a[2],b[2],c[2],d[2]}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose4x3(T& x, T& y, T& z, const T& a, const T& b, const T& c, const T& d);
|
||||
|
||||
// Transpose a 2x4 matrix
|
||||
// a = {x[0],y[0],undetermined,undetermined}
|
||||
// b = {x[1],y[1],undetermined,undetermined}
|
||||
// c = {x[2],y[2],undetermined,undetermined}
|
||||
// d = {x[3],y[3],undetermined,undetermined}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose2x4(T& a, T& b, T& c, T& d, const T& x, const T& y);
|
||||
|
||||
// Transpose a 2x4 matrix
|
||||
// x = {a[0],b[0],c[0],d[0]}
|
||||
// y = {a[1],b[1],c[1],d[1]}
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose4x2(T& x, T& y, const T& a, const T& b, const T& c, const T& d);
|
||||
|
||||
} // namespace simd
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
#if defined(AYA_SIMD_USE_SSE)
|
||||
#include "simd/simd_sse.inl"
|
||||
#elif defined(AYA_SIMD_USE_NEON)
|
||||
#include "simd/simd_neon.inl"
|
||||
#endif
|
||||
|
||||
#include "simd/simd_common.inl"
|
||||
154
engine/core/src/simd/simd_common.inl
Normal file
154
engine/core/src/simd/simd_common.inl
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
// SIMD common platform independent implementation
|
||||
|
||||
#include "simd/simd.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace simd
|
||||
{
|
||||
|
||||
namespace details
|
||||
{
|
||||
AYA_SIMD_INLINE v4f inverseEstimate0Precision()
|
||||
{
|
||||
return splat(3e-04f);
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f inverseEstimate1Precision()
|
||||
{
|
||||
return splat(2e-07f);
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f inverseSqrtEstimate0Precision()
|
||||
{
|
||||
return splat(3.3e-05f);
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f inverseSqrtEstimate1Precision()
|
||||
{
|
||||
return splat(3e-07f);
|
||||
}
|
||||
} // namespace details
|
||||
|
||||
AYA_SIMD_INLINE v4f sumAcross2(v4fArg a, v4fArg b, v4fArg c)
|
||||
{
|
||||
return sumAcross2(a, b, c, c);
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f sumAcross3(v4fArg a, v4fArg b, v4fArg c)
|
||||
{
|
||||
return sumAcross3(a, b, c, c);
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f sumAcross4(v4fArg a, v4fArg b, v4fArg c)
|
||||
{
|
||||
return sumAcross4(a, b, c, c);
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f sumAcross2(v4fArg a, v4fArg b, v4fArg c, v4fArg d)
|
||||
{
|
||||
v4f a0c0a1c1 = zipLow(a, c);
|
||||
v4f b0d0b1d1 = zipLow(b, d);
|
||||
v4f a0b0c0d0, a1b1c1d1;
|
||||
zip(a0b0c0d0, a1b1c1d1, a0c0a1c1, b0d0b1d1);
|
||||
v4f sum = a0b0c0d0 + a1b1c1d1;
|
||||
return sum;
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f sumAcross3(v4fArg a, v4fArg b, v4fArg c, v4fArg d)
|
||||
{
|
||||
v4f a0c0a1c1, a2c2xxxx;
|
||||
zip(a0c0a1c1, a2c2xxxx, a, c);
|
||||
v4f b0d0b1d1, b2d2xxxx;
|
||||
zip(b0d0b1d1, b2d2xxxx, b, d);
|
||||
v4f a0b0c0d0, a1b1c1d1;
|
||||
zip(a0b0c0d0, a1b1c1d1, a0c0a1c1, b0d0b1d1);
|
||||
v4f sum = a0b0c0d0 + a1b1c1d1;
|
||||
v4f a2b2c2d2 = zipLow(a2c2xxxx, b2d2xxxx);
|
||||
sum = sum + a2b2c2d2;
|
||||
return sum;
|
||||
}
|
||||
|
||||
AYA_SIMD_INLINE v4f sumAcross4(v4fArg a, v4fArg b, v4fArg c, v4fArg d)
|
||||
{
|
||||
v4f a0c0a1c1, a2c2a3c3;
|
||||
zip(a0c0a1c1, a2c2a3c3, a, c);
|
||||
v4f b0d0b1d1, b2d2b3d3;
|
||||
zip(b0d0b1d1, b2d2b3d3, b, d);
|
||||
v4f a0b0c0d0, a1b1c1d1;
|
||||
zip(a0b0c0d0, a1b1c1d1, a0c0a1c1, b0d0b1d1);
|
||||
v4f sum = a0b0c0d0 + a1b1c1d1;
|
||||
v4f a2b2c2d2, a3b3c3d3;
|
||||
zip(a2b2c2d2, a3b3c3d3, a2c2a3c3, b2d2b3d3);
|
||||
sum = sum + (a2b2c2d2 + a3b3c3d3);
|
||||
return sum;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose(T& a, T& b, T& c, T& d, const T& x, const T& y, const T& z, const T& w)
|
||||
{
|
||||
T x0z0x1z1, x2z2x3z3;
|
||||
zip(x0z0x1z1, x2z2x3z3, x, z);
|
||||
T y0w0y1w1, y2w2y3w3;
|
||||
zip(y0w0y1w1, y2w2y3w3, y, w);
|
||||
|
||||
zip(a, b, x0z0x1z1, y0w0y1w1);
|
||||
zip(c, d, x2z2x3z3, y2w2y3w3);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose4x3(T& x, T& y, T& z, const T& a, const T& b, const T& c, const T& d)
|
||||
{
|
||||
T dummy;
|
||||
transpose(x, y, z, dummy, a, b, c, d);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose3x4(T& a, T& b, T& c, T& d, const T& x, const T& y, const T& z)
|
||||
{
|
||||
transpose(a, b, c, d, x, y, z, z);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose4x2(T& x, T& y, const T& a, const T& b, const T& c, const T& d)
|
||||
{
|
||||
T t0 = zipLow(a, c);
|
||||
T t1 = zipLow(b, d);
|
||||
zip(x, y, t0, t1);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE void transpose2x4(T& a, T& b, T& c, T& d, const T& x, const T& y)
|
||||
{
|
||||
zip(a, c, x, y);
|
||||
b = moveHighLow(a, a);
|
||||
d = moveHighLow(c, c);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE T gatherX(const T& a, const T& b, const T& c, const T& d)
|
||||
{
|
||||
T t0 = zipLow(a, c);
|
||||
T t1 = zipLow(b, d);
|
||||
return zipLow(t0, t1);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE T gatherX(const T& a, const T& b, const T& c)
|
||||
{
|
||||
T t0 = zipLow(a, c);
|
||||
return zipLow(t0, b);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
AYA_SIMD_INLINE T gatherX(const T& a, const T& b)
|
||||
{
|
||||
return zipLow(a, b);
|
||||
}
|
||||
|
||||
} // namespace simd
|
||||
|
||||
} // namespace Aya
|
||||
1542
engine/core/src/simd/simd_neon.inl
Normal file
1542
engine/core/src/simd/simd_neon.inl
Normal file
File diff suppressed because it is too large
Load Diff
89
engine/core/src/simd/simd_platform.hpp
Normal file
89
engine/core/src/simd/simd_platform.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__i386__) || defined(_M_IX86) && !defined(_M_ARM64) && !defined(__aarch64__) // __i386__ on clang, _M_IX86 on MVC
|
||||
#define AYA_SIMD_X86
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64) && !defined(_M_ARM64) && !defined(__aarch64__) // __x86_64__ on clang, _M_X64 on MVC
|
||||
#define AYA_SIMD_X64
|
||||
#endif
|
||||
|
||||
#if defined(__ARM_NEON) || defined(__arm__) || defined(_M_ARM) || \
|
||||
defined(_M_ARM64) // __ARM_NEON on apple clang for iOS, __arm__ on GCC, _M_ARM in MVC for Windows Phone
|
||||
#define AYA_SIMD_ARM
|
||||
#endif
|
||||
|
||||
#if defined(AYA_SIMD_X86) || defined(AYA_SIMD_X64) && !defined(_M_ARM64) && !defined(__aarch64__)
|
||||
#define AYA_SIMD_USE_SSE
|
||||
#elif defined(AYA_SIMD_ARM) || defined(_M_ARM64)
|
||||
#define AYA_SIMD_USE_NEON
|
||||
#endif
|
||||
|
||||
#ifdef AYA_SIMD_USE_SSE
|
||||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
#include <mmintrin.h>
|
||||
#endif
|
||||
|
||||
#ifdef AYA_SIMD_USE_NEON
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#define AYA_SIMD_ALIGN_ASSERT(p, a) AYAASSERT_VERY_FAST(((uint64_t)(p) & (a - 1)) == 0)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define AYA_SIMD_INLINE __forceinline
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
#define AYA_SIMD_INLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
#define AYA_SIMD_INLINE inline
|
||||
#endif
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace simd
|
||||
{
|
||||
|
||||
namespace details
|
||||
{
|
||||
#if defined(AYA_SIMD_USE_SSE)
|
||||
typedef __m128 vec4f_t;
|
||||
typedef __m128i vec4i_t;
|
||||
typedef __m128i vec4u_t;
|
||||
#elif defined(AYA_SIMD_USE_NEON)
|
||||
typedef float32x4_t vec4f_t;
|
||||
typedef int32x4_t vec4i_t;
|
||||
typedef uint32x4_t vec4u_t;
|
||||
#endif
|
||||
|
||||
template<class ScalarType>
|
||||
struct VectorTypeSelect;
|
||||
|
||||
template<>
|
||||
struct VectorTypeSelect<float>
|
||||
{
|
||||
typedef vec4f_t type;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct VectorTypeSelect<int32_t>
|
||||
{
|
||||
typedef vec4i_t type;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct VectorTypeSelect<uint32_t>
|
||||
{
|
||||
typedef vec4u_t type;
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
} // namespace simd
|
||||
|
||||
} // namespace Aya
|
||||
1222
engine/core/src/simd/simd_sse.inl
Normal file
1222
engine/core/src/simd/simd_sse.inl
Normal file
File diff suppressed because it is too large
Load Diff
56
engine/core/src/simd/simd_types.hpp
Normal file
56
engine/core/src/simd/simd_types.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
// Include this file if you only need the SIMD types but not the functionality (which could be a large include)
|
||||
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "simd/simd_platform.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace simd
|
||||
{
|
||||
|
||||
template<class ElemType>
|
||||
class v4
|
||||
{
|
||||
public:
|
||||
typedef typename details::VectorTypeSelect<ElemType>::type fun_t;
|
||||
typedef ElemType elem_t;
|
||||
typedef v4<elem_t> v4_t;
|
||||
typedef const v4_t& arg_t;
|
||||
typedef fun_t pod_t;
|
||||
|
||||
AYA_SIMD_INLINE v4() {}
|
||||
AYA_SIMD_INLINE v4(const v4& u);
|
||||
AYA_SIMD_INLINE v4(const pod_t& u);
|
||||
AYA_SIMD_INLINE void operator=(const v4& u);
|
||||
AYA_SIMD_INLINE void operator=(const pod_t& u);
|
||||
AYA_SIMD_INLINE operator pod_t() const;
|
||||
|
||||
fun_t v;
|
||||
};
|
||||
|
||||
// The SIMD types
|
||||
typedef v4<float> v4f;
|
||||
typedef v4<int32_t> v4i;
|
||||
typedef v4<uint32_t> v4u;
|
||||
|
||||
// The SIMD pod types
|
||||
typedef v4f::pod_t v4f_pod;
|
||||
typedef v4i::pod_t v4i_pod;
|
||||
typedef v4u::pod_t v4u_pod;
|
||||
|
||||
// Shortcuts for const refs
|
||||
typedef const v4f& v4fArg;
|
||||
typedef const v4i& v4iArg;
|
||||
typedef const v4u& v4uArg;
|
||||
|
||||
} // namespace simd
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
#include "simd/simd_types.inl"
|
||||
45
engine/core/src/simd/simd_types.inl
Normal file
45
engine/core/src/simd/simd_types.inl
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
// Do not include this file directly
|
||||
|
||||
#include "simd/simd_types.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
namespace simd
|
||||
{
|
||||
|
||||
template<class ElemType>
|
||||
AYA_SIMD_INLINE v4<ElemType>::v4(const v4& u)
|
||||
: v(u.v)
|
||||
{
|
||||
}
|
||||
|
||||
template<class ElemType>
|
||||
AYA_SIMD_INLINE v4<ElemType>::v4(const pod_t& u)
|
||||
: v(u)
|
||||
{
|
||||
}
|
||||
|
||||
template<class ElemType>
|
||||
AYA_SIMD_INLINE void v4<ElemType>::operator=(const v4& u)
|
||||
{
|
||||
v = u.v;
|
||||
}
|
||||
|
||||
template<class ElemType>
|
||||
AYA_SIMD_INLINE void v4<ElemType>::operator=(const pod_t& u)
|
||||
{
|
||||
v = u;
|
||||
}
|
||||
|
||||
template<class ElemType>
|
||||
AYA_SIMD_INLINE v4<ElemType>::operator pod_t() const
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace simd
|
||||
|
||||
} // namespace Aya
|
||||
61
engine/core/src/stringbuffer.hpp
Normal file
61
engine/core/src/stringbuffer.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "Debug.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
class StringReadBuffer
|
||||
{
|
||||
std::string::const_iterator cur;
|
||||
const std::string& buffer;
|
||||
|
||||
public:
|
||||
StringReadBuffer(const std::string& str)
|
||||
: buffer(str)
|
||||
{
|
||||
cur = buffer.begin();
|
||||
}
|
||||
|
||||
StringReadBuffer& operator>>(unsigned char& value)
|
||||
{
|
||||
AYAASSERT(cur != buffer.end());
|
||||
if (cur == buffer.end())
|
||||
{
|
||||
throw Aya::runtime_error("Reading past end of string");
|
||||
}
|
||||
else
|
||||
{
|
||||
value = *cur++;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool eof()
|
||||
{
|
||||
return cur == buffer.end();
|
||||
}
|
||||
};
|
||||
|
||||
class StringWriteBuffer
|
||||
{
|
||||
std::string buffer;
|
||||
|
||||
public:
|
||||
StringWriteBuffer()
|
||||
: buffer(std::string())
|
||||
{
|
||||
}
|
||||
|
||||
StringWriteBuffer& operator<<(unsigned char value)
|
||||
{
|
||||
buffer.push_back(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string& str()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
} // namespace Aya
|
||||
572
engine/core/src/threadsafe.hpp
Normal file
572
engine/core/src/threadsafe.hpp
Normal file
@@ -0,0 +1,572 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include "boost.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "time.hpp"
|
||||
#include "atomic.hpp"
|
||||
#include "boost/shared_ptr.hpp"
|
||||
#include "boost/noncopyable.hpp"
|
||||
#include "AyaFormat.hpp"
|
||||
#include "FastLog.hpp"
|
||||
|
||||
#include "AyaPlatform.hpp"
|
||||
|
||||
#include "boost/thread/mutex.hpp"
|
||||
|
||||
using boost::shared_ptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
LOGGROUP(MutexLifetime);
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
// A lightweight mutex that uses CRITICAL_SECTION under Windows.
|
||||
// This mutex is non-recursive.
|
||||
class mutex
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CRITICAL_SECTION cs;
|
||||
|
||||
public:
|
||||
mutex()
|
||||
{
|
||||
::InitializeCriticalSection(&cs);
|
||||
FASTLOG1(FLog::MutexLifetime, "Aya::mutext init m = 0x%x", this);
|
||||
}
|
||||
~mutex()
|
||||
{
|
||||
::DeleteCriticalSection(&cs);
|
||||
FASTLOG1(FLog::MutexLifetime, "Aya::mutext destroy m = 0x%x", this);
|
||||
}
|
||||
class scoped_lock : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
scoped_lock(mutex& m)
|
||||
: m(m)
|
||||
{
|
||||
::EnterCriticalSection(&m.cs);
|
||||
}
|
||||
~scoped_lock()
|
||||
{
|
||||
::LeaveCriticalSection(&m.cs);
|
||||
}
|
||||
|
||||
private:
|
||||
mutex& m;
|
||||
};
|
||||
#else
|
||||
pthread_mutex_t sl;
|
||||
|
||||
public:
|
||||
mutex()
|
||||
{
|
||||
if (pthread_mutex_init(&sl, NULL) != 0)
|
||||
throw std::runtime_error("failed in mutex to initialize pthread_mutex_init.");
|
||||
}
|
||||
~mutex()
|
||||
{
|
||||
if (pthread_mutex_destroy(&sl) != 0)
|
||||
{
|
||||
// printf("Error at pthread_spin_destroy()");
|
||||
}
|
||||
}
|
||||
class scoped_lock : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
scoped_lock(mutex& m0)
|
||||
: m(m0)
|
||||
{
|
||||
int rc = pthread_mutex_lock(&m.sl);
|
||||
if (rc != 0)
|
||||
{
|
||||
// fprintf(stderr,"Test FAILED: child failed to get spin lock,error code:%d\n" , rc);
|
||||
}
|
||||
//::EnterCriticalSection(&m.sl);
|
||||
}
|
||||
~scoped_lock()
|
||||
{
|
||||
if (pthread_mutex_unlock(&m.sl) != 0)
|
||||
{
|
||||
// fprintf(stderr,"child: Error at pthread_spin_unlock()\n");
|
||||
}
|
||||
//::LeaveCriticalSection(&m.sl);
|
||||
}
|
||||
|
||||
private:
|
||||
mutex& m;
|
||||
};
|
||||
#endif
|
||||
};
|
||||
|
||||
// calls AYACRASH() on contention.
|
||||
class concurrency_catcher : boost::noncopyable
|
||||
{
|
||||
Aya::atomic<int> value;
|
||||
static const long unlocked = 0;
|
||||
static const long locked = 1;
|
||||
|
||||
public:
|
||||
concurrency_catcher()
|
||||
: value(unlocked)
|
||||
{
|
||||
}
|
||||
class scoped_lock : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
scoped_lock(concurrency_catcher& m);
|
||||
~scoped_lock();
|
||||
|
||||
private:
|
||||
concurrency_catcher& m;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calls AYACRASH() on contention.
|
||||
struct reentrant_concurrency_catcher : boost::noncopyable
|
||||
{
|
||||
Aya::atomic<int> value;
|
||||
volatile unsigned long threadId;
|
||||
static const long unlocked = 0;
|
||||
static const long locked = 1;
|
||||
static const unsigned long noThreadId;
|
||||
|
||||
public:
|
||||
reentrant_concurrency_catcher()
|
||||
: value(unlocked)
|
||||
, threadId(noThreadId)
|
||||
{
|
||||
}
|
||||
class scoped_lock : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
scoped_lock(reentrant_concurrency_catcher& m);
|
||||
~scoped_lock();
|
||||
|
||||
private:
|
||||
bool isChild;
|
||||
reentrant_concurrency_catcher& m;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class readwrite_concurrency_catcher : boost::noncopyable
|
||||
{
|
||||
friend class scoped_write_request;
|
||||
friend class scoped_read_request;
|
||||
Aya::atomic<int> write_requested;
|
||||
Aya::atomic<int> read_requested;
|
||||
static const long unlocked = 0;
|
||||
static const long locked = 1;
|
||||
|
||||
public:
|
||||
readwrite_concurrency_catcher()
|
||||
: write_requested(unlocked)
|
||||
, read_requested(0) {};
|
||||
class scoped_write_request
|
||||
{
|
||||
readwrite_concurrency_catcher& m;
|
||||
|
||||
public:
|
||||
// Place this code around tasks that write to a DataModel
|
||||
scoped_write_request(readwrite_concurrency_catcher& mt);
|
||||
~scoped_write_request();
|
||||
};
|
||||
class scoped_read_request
|
||||
{
|
||||
readwrite_concurrency_catcher& m;
|
||||
|
||||
public:
|
||||
// Place this code around tasks that write to a DataModel
|
||||
scoped_read_request(readwrite_concurrency_catcher& m);
|
||||
~scoped_read_request();
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class spin_mutex
|
||||
{
|
||||
Aya::atomic<int> sl;
|
||||
|
||||
public:
|
||||
spin_mutex()
|
||||
{
|
||||
// init
|
||||
}
|
||||
|
||||
~spin_mutex()
|
||||
{
|
||||
// destroy
|
||||
}
|
||||
|
||||
bool try_lock()
|
||||
{
|
||||
return sl.compare_and_swap(1, 0) == 0;
|
||||
}
|
||||
|
||||
void lock()
|
||||
{
|
||||
while (sl.compare_and_swap(1, 0) != 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
void unlock()
|
||||
{
|
||||
sl.compare_and_swap(0, 1);
|
||||
}
|
||||
|
||||
class scoped_lock : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
scoped_lock(spin_mutex& m0)
|
||||
: m(m0)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (m.try_lock())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
~scoped_lock()
|
||||
{
|
||||
m.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
spin_mutex& m;
|
||||
};
|
||||
};
|
||||
|
||||
// Use this queue when you want fast performance, low
|
||||
// resource usage, and the queue is not very busy.
|
||||
// For very busy queues, use tbb::concurrent_queue (correction, we dont have tbb anymore).
|
||||
template<typename T>
|
||||
class safe_queue : boost::noncopyable
|
||||
{
|
||||
protected:
|
||||
std::queue<T> queue;
|
||||
// TODO: spin_mutex is possibly a bad choice for expensive T types
|
||||
typedef spin_mutex mutex;
|
||||
mutex m;
|
||||
|
||||
public:
|
||||
void clear()
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
|
||||
while (!queue.empty())
|
||||
queue.pop();
|
||||
}
|
||||
void push(const T& value)
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
queue.push(value);
|
||||
}
|
||||
bool pop_if_present(T& value)
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
if (!queue.empty())
|
||||
{
|
||||
value = queue.front();
|
||||
queue.pop();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pop_if_present()
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
if (!queue.empty())
|
||||
{
|
||||
queue.pop();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// WARNING: Peeking has side effects, if T has copy constructors and destructors
|
||||
bool peek_if_present(T& value)
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
if (!queue.empty())
|
||||
{
|
||||
value = queue.front();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lock and spin-free calls:
|
||||
inline size_t size() const
|
||||
{
|
||||
return queue.size();
|
||||
}
|
||||
inline bool empty() const
|
||||
{
|
||||
return queue.empty();
|
||||
}
|
||||
};
|
||||
|
||||
namespace implementation
|
||||
{
|
||||
template<typename T>
|
||||
struct timestamped_safe_queue_item
|
||||
{
|
||||
T value;
|
||||
Aya::Time timestamp;
|
||||
timestamped_safe_queue_item() {}
|
||||
timestamped_safe_queue_item(const T& value)
|
||||
: timestamp(Aya::Time::now<Aya::Time::Fast>())
|
||||
, value(value)
|
||||
{
|
||||
}
|
||||
};
|
||||
} // namespace implementation
|
||||
template<typename T>
|
||||
class timestamped_safe_queue : protected safe_queue<implementation::timestamped_safe_queue_item<T>>
|
||||
{
|
||||
typedef safe_queue<implementation::timestamped_safe_queue_item<T>> Super;
|
||||
double headTimestamp;
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// GCC/Clang won't inherit the mutex type defined in Super. Therefore we redeclare it here. Yuck!
|
||||
typedef spin_mutex mutex;
|
||||
#endif
|
||||
public:
|
||||
void clear()
|
||||
{
|
||||
headTimestamp = 0.f;
|
||||
Super::clear();
|
||||
}
|
||||
|
||||
void push(const T& value)
|
||||
{
|
||||
implementation::timestamped_safe_queue_item<T> item(value);
|
||||
Super::push(item);
|
||||
headTimestamp = item.timestamp.timestampSeconds();
|
||||
}
|
||||
|
||||
bool pop_if_present(T& value)
|
||||
{
|
||||
mutex::scoped_lock lock(this->m);
|
||||
if (!this->queue.empty())
|
||||
{
|
||||
value = this->queue.front().value;
|
||||
this->queue.pop();
|
||||
if (!this->queue.empty())
|
||||
{
|
||||
headTimestamp = this->queue.front().timestamp.timestampSeconds();
|
||||
}
|
||||
else
|
||||
{
|
||||
headTimestamp = 0.f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// pops the head item if it has been waiting at least waitTime
|
||||
bool pop_if_waited(Aya::Time::Interval waitTime, T& value)
|
||||
{
|
||||
mutex::scoped_lock lock(this->m);
|
||||
if (this->queue.empty())
|
||||
return false;
|
||||
if (Aya::Time::now<Aya::Time::Fast>() < this->queue.front().timestamp + waitTime)
|
||||
return false;
|
||||
value = this->queue.front().value;
|
||||
this->queue.pop();
|
||||
if (!this->queue.empty())
|
||||
{
|
||||
headTimestamp = this->queue.front().timestamp.timestampSeconds();
|
||||
}
|
||||
else
|
||||
{
|
||||
headTimestamp = 0.f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the time that the head item has been waiting or zero.
|
||||
double head_waittime_sec(const Aya::Time& timeNow) const
|
||||
{
|
||||
if (headTimestamp > 0.f)
|
||||
{
|
||||
return timeNow.timestampSeconds() - headTimestamp;
|
||||
}
|
||||
else
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
inline size_t size() const
|
||||
{
|
||||
return this->queue.size();
|
||||
}
|
||||
inline bool empty() const
|
||||
{
|
||||
return this->queue.empty();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
class safe_heap : boost::noncopyable
|
||||
{
|
||||
std::vector<T> vector;
|
||||
// TODO: spin_mutex is possibly a bad choice for expensive T types
|
||||
typedef spin_mutex mutex;
|
||||
mutex m;
|
||||
|
||||
public:
|
||||
void clear()
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
vector.clear();
|
||||
}
|
||||
void push_heap(const T& value)
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
vector.push_back(value);
|
||||
std::push_heap(vector.begin(), vector.end());
|
||||
}
|
||||
bool pop_heap_if_present(T& value)
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
if (!vector.empty())
|
||||
{
|
||||
std::pop_heap(vector.begin(), vector.end());
|
||||
value = vector.back();
|
||||
vector.pop_back();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pop_heap_if_present()
|
||||
{
|
||||
mutex::scoped_lock lock(m);
|
||||
if (!vector.empty())
|
||||
{
|
||||
std::pop_heap(vector.begin(), vector.end());
|
||||
vector.pop_back();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lock and spin-free calls:
|
||||
inline size_t size() const
|
||||
{
|
||||
return vector.size();
|
||||
}
|
||||
inline bool empty() const
|
||||
{
|
||||
return vector.empty();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#define SAFE_STATIC(TYPE, NAME) \
|
||||
static TYPE* safe_static_do_get_##NAME() \
|
||||
{ \
|
||||
static TYPE value; \
|
||||
return &value; \
|
||||
} \
|
||||
static void safe_static_init_##NAME() \
|
||||
{ \
|
||||
safe_static_do_get_##NAME(); \
|
||||
} \
|
||||
static TYPE& NAME() \
|
||||
{ \
|
||||
static boost::once_flag once_init_##NAME = BOOST_ONCE_INIT; \
|
||||
boost::call_once(safe_static_init_##NAME, once_init_##NAME); \
|
||||
return *safe_static_do_get_##NAME(); \
|
||||
}
|
||||
|
||||
#define SAFE_HEAP_STATIC(TYPE, NAME) \
|
||||
static TYPE* safe_static_do_get_##NAME() \
|
||||
{ \
|
||||
static TYPE* value = new TYPE; \
|
||||
return value; \
|
||||
} \
|
||||
static void safe_static_init_##NAME() \
|
||||
{ \
|
||||
safe_static_do_get_##NAME(); \
|
||||
} \
|
||||
static TYPE& NAME() \
|
||||
{ \
|
||||
static boost::once_flag once_init_##NAME = BOOST_ONCE_INIT; \
|
||||
boost::call_once(safe_static_init_##NAME, once_init_##NAME); \
|
||||
return *safe_static_do_get_##NAME(); \
|
||||
}
|
||||
|
||||
|
||||
// A wrapper around thread_specific_ptr that lets you
|
||||
// have a thread-specific reference to an object
|
||||
template<typename T>
|
||||
class thread_specific_reference
|
||||
{
|
||||
typedef T* TPTR;
|
||||
boost::thread_specific_ptr<TPTR> ptr;
|
||||
|
||||
public:
|
||||
T* get()
|
||||
{
|
||||
TPTR* p = ptr.get();
|
||||
if (p)
|
||||
return *p;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
void reset(T* value)
|
||||
{
|
||||
TPTR* p = new TPTR(value);
|
||||
ptr.reset(p);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A wrapper around thread_specific_ptr that lets you
|
||||
// have a thread-specific shared_ptr to an object
|
||||
template<typename T>
|
||||
class thread_specific_shared_ptr : boost::noncopyable
|
||||
{
|
||||
typedef shared_ptr<T> TPTR;
|
||||
boost::thread_specific_ptr<TPTR> ptr;
|
||||
|
||||
public:
|
||||
operator shared_ptr<T>() const
|
||||
{
|
||||
TPTR* p = ptr.get();
|
||||
if (p)
|
||||
return *p;
|
||||
else
|
||||
return shared_ptr<T>();
|
||||
}
|
||||
|
||||
void reset(shared_ptr<T> value)
|
||||
{
|
||||
TPTR* p = new TPTR(value);
|
||||
ptr.reset(p);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Aya
|
||||
171
engine/core/src/time.cpp
Normal file
171
engine/core/src/time.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
// This prevent inclusion of winsock.h in Windows.h, which prevents windows redifinition errors
|
||||
// Look at winsock2.h for details, winsock2.h is #included from boost.hpp & other places.
|
||||
#ifdef _WIN32
|
||||
#define _WINSOCKAPI_
|
||||
#endif
|
||||
|
||||
#if defined(__linux) || defined(__APPLE__)
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "time.hpp"
|
||||
#include "Debug.hpp"
|
||||
#include "atomic.hpp"
|
||||
#include <stdexcept>
|
||||
#include "FastLog.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(AYA_PLATFORM_DURANGO)
|
||||
#include <windows.h>
|
||||
#include <timeapi.h>
|
||||
|
||||
#endif
|
||||
|
||||
FASTINTVARIABLE(SpeedTestPeriodMillis, 1000)
|
||||
FASTINTVARIABLE(MaxSpeedDeltaMillis, 300)
|
||||
FASTINTVARIABLE(SpeedCountCap, 5)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
long long Time::getTickCount()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
LARGE_INTEGER ticks;
|
||||
int rval = QueryPerformanceCounter(&ticks);
|
||||
return ticks.QuadPart;
|
||||
#elif defined(__APPLE__)
|
||||
uint64_t ticks = mach_absolute_time();
|
||||
return ticks;
|
||||
#elif defined(__ANDROID__) || defined(__linux)
|
||||
timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
return now.tv_sec * 1e9 + now.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static double getPerformanceFrequency()
|
||||
{
|
||||
static double frequency = 0.0;
|
||||
if (frequency == 0.0)
|
||||
{
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
frequency = static_cast<double>(freq.QuadPart);
|
||||
}
|
||||
return frequency;
|
||||
}
|
||||
#endif
|
||||
|
||||
long long Time::getStart()
|
||||
{
|
||||
// not worried about potential multi-threaded double-init.
|
||||
// assumptions: underlying type is long long.
|
||||
static const long long start = getTickCount();
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
Time::SampleMethod Time::preciseOverride = Time::Precise;
|
||||
|
||||
template<>
|
||||
Time Time::now<Time::Precise>()
|
||||
{
|
||||
Time result;
|
||||
#if defined(_WIN32)
|
||||
result.sec = static_cast<double>(getTickCount() - getStart()) / getPerformanceFrequency();
|
||||
#else
|
||||
result.sec = (getTickCount() - getStart()) * 1e-9;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
Time Time::now<Time::Multimedia>()
|
||||
{
|
||||
#if defined(_WIN32) && !defined(AYA_PLATFORM_DURANGO)
|
||||
return Time(timeGetTime() / 1000.0);
|
||||
#else
|
||||
// TODO: Is this fast enough on Mac?
|
||||
return now<Time::Precise>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
Time Time::now<Time::Fast>()
|
||||
{
|
||||
return now<Time::Precise>();
|
||||
}
|
||||
|
||||
Time Time::nowFast()
|
||||
{
|
||||
return now<Time::Fast>();
|
||||
}
|
||||
|
||||
double Time::nowFastSec()
|
||||
{
|
||||
return Time::nowFast().timestampSeconds();
|
||||
}
|
||||
|
||||
template<>
|
||||
Time Time::now<Time::Benchmark>()
|
||||
{
|
||||
if (preciseOverride <= Benchmark)
|
||||
return now<Precise>();
|
||||
else
|
||||
return now<Fast>();
|
||||
}
|
||||
|
||||
Time Time::now(SampleMethod sampleMethod)
|
||||
{
|
||||
switch (sampleMethod)
|
||||
{
|
||||
default:
|
||||
case Fast:
|
||||
return now<Fast>();
|
||||
|
||||
case Precise:
|
||||
return now<Precise>();
|
||||
|
||||
case Benchmark:
|
||||
return now<Benchmark>();
|
||||
|
||||
case Multimedia:
|
||||
return now<Multimedia>();
|
||||
}
|
||||
}
|
||||
|
||||
Time::Interval operator-(const Time& t1, const Time& t0)
|
||||
{
|
||||
const double seconds = t1.sec - t0.sec;
|
||||
return Time::Interval(seconds);
|
||||
}
|
||||
|
||||
void Time::Interval::sleep()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Translate to milliseconds
|
||||
Sleep((int)(sec * 1e3));
|
||||
#else
|
||||
// Translate to microseconds
|
||||
usleep((int)(sec * 1e6));
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user