mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
Upgrade libsrt to v1.5.3. v5.0.183 (#3808)
This commit is contained in:
parent
389a62ee3a
commit
632d457194
154 changed files with 39813 additions and 17038 deletions
405
trunk/3rdparty/srt-1-fit/srtcore/utilities.h
vendored
Executable file → Normal file
405
trunk/3rdparty/srt-1-fit/srtcore/utilities.h
vendored
Executable file → Normal file
|
@ -13,72 +13,14 @@ written by
|
|||
Haivision Systems Inc.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef INC__SRT_UTILITIES_H
|
||||
#define INC__SRT_UTILITIES_H
|
||||
|
||||
|
||||
#ifdef __GNUG__
|
||||
#define ATR_UNUSED __attribute__((unused))
|
||||
#define ATR_DEPRECATED __attribute__((deprecated))
|
||||
#else
|
||||
#define ATR_UNUSED
|
||||
#define ATR_DEPRECATED
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus > 199711L
|
||||
#define HAVE_CXX11 1
|
||||
|
||||
// For gcc 4.7, claim C++11 is supported, as long as experimental C++0x is on,
|
||||
// however it's only the "most required C++11 support".
|
||||
#if defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7 // 4.7 only!
|
||||
#define ATR_NOEXCEPT
|
||||
#define ATR_CONSTEXPR
|
||||
#define ATR_OVERRIDE
|
||||
#define ATR_FINAL
|
||||
#else
|
||||
#define HAVE_FULL_CXX11 1
|
||||
#define ATR_NOEXCEPT noexcept
|
||||
#define ATR_CONSTEXPR constexpr
|
||||
#define ATR_OVERRIDE override
|
||||
#define ATR_FINAL final
|
||||
#endif
|
||||
|
||||
// Microsoft Visual Studio supports C++11, but not fully,
|
||||
// and still did not change the value of __cplusplus. Treat
|
||||
// this special way.
|
||||
// _MSC_VER == 1800 means Microsoft Visual Studio 2013.
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1800
|
||||
#define HAVE_CXX11 1
|
||||
#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026
|
||||
#define HAVE_FULL_CXX11 1
|
||||
#define ATR_NOEXCEPT noexcept
|
||||
#define ATR_CONSTEXPR constexpr
|
||||
#define ATR_OVERRIDE override
|
||||
#define ATR_FINAL final
|
||||
#else
|
||||
#define ATR_NOEXCEPT
|
||||
#define ATR_CONSTEXPR
|
||||
#define ATR_OVERRIDE
|
||||
#define ATR_FINAL
|
||||
#endif
|
||||
#else
|
||||
#define HAVE_CXX11 0
|
||||
#define ATR_NOEXCEPT // throw() - bad idea
|
||||
#define ATR_CONSTEXPR
|
||||
#define ATR_OVERRIDE
|
||||
#define ATR_FINAL
|
||||
|
||||
#endif
|
||||
|
||||
#if !HAVE_CXX11 && defined(REQUIRE_CXX11) && REQUIRE_CXX11 == 1
|
||||
#error "The currently compiled application required C++11, but your compiler doesn't support it."
|
||||
#endif
|
||||
|
||||
#ifndef INC_SRT_UTILITIES_H
|
||||
#define INC_SRT_UTILITIES_H
|
||||
|
||||
// Windows warning disabler
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
|
||||
#include "platform_sys.h"
|
||||
#include "srt_attr_defs.h" // defines HAVE_CXX11
|
||||
|
||||
// Happens that these are defined, undefine them in advance
|
||||
#undef min
|
||||
|
@ -88,10 +30,11 @@ written by
|
|||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#if HAVE_CXX11
|
||||
#include <type_traits>
|
||||
|
@ -100,6 +43,7 @@ written by
|
|||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
// -------------- UTILITIES ------------------------
|
||||
|
||||
|
@ -113,7 +57,7 @@ written by
|
|||
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__CYGWIN__) || defined(__GNU__)
|
||||
#if defined(__linux__) || defined(__CYGWIN__) || defined(__GNU__) || defined(__GLIBC__)
|
||||
|
||||
# include <endian.h>
|
||||
|
||||
|
@ -171,7 +115,7 @@ written by
|
|||
|
||||
# include <sys/endian.h>
|
||||
|
||||
#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
|
||||
#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
|
||||
|
||||
# include <sys/endian.h>
|
||||
|
||||
|
@ -196,6 +140,46 @@ written by
|
|||
# define le64toh(x) letoh64(x)
|
||||
#endif
|
||||
|
||||
#elif defined(SUNOS)
|
||||
|
||||
// SunOS/Solaris
|
||||
|
||||
#include <sys/byteorder.h>
|
||||
#include <sys/isa_defs.h>
|
||||
|
||||
#define __LITTLE_ENDIAN 1234
|
||||
#define __BIG_ENDIAN 4321
|
||||
|
||||
# if defined(_BIG_ENDIAN)
|
||||
#define __BYTE_ORDER __BIG_ENDIAN
|
||||
#define be64toh(x) (x)
|
||||
#define be32toh(x) (x)
|
||||
#define be16toh(x) (x)
|
||||
#define le16toh(x) ((uint16_t)BSWAP_16(x))
|
||||
#define le32toh(x) BSWAP_32(x)
|
||||
#define le64toh(x) BSWAP_64(x)
|
||||
#define htobe16(x) (x)
|
||||
#define htole16(x) ((uint16_t)BSWAP_16(x))
|
||||
#define htobe32(x) (x)
|
||||
#define htole32(x) BSWAP_32(x)
|
||||
#define htobe64(x) (x)
|
||||
#define htole64(x) BSWAP_64(x)
|
||||
# else
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#define be64toh(x) BSWAP_64(x)
|
||||
#define be32toh(x) ntohl(x)
|
||||
#define be16toh(x) ntohs(x)
|
||||
#define le16toh(x) (x)
|
||||
#define le32toh(x) (x)
|
||||
#define le64toh(x) (x)
|
||||
#define htobe16(x) htons(x)
|
||||
#define htole16(x) (x)
|
||||
#define htobe32(x) htonl(x)
|
||||
#define htole32(x) (x)
|
||||
#define htobe64(x) BSWAP_64(x)
|
||||
#define htole64(x) (x)
|
||||
# endif
|
||||
|
||||
#elif defined(__WINDOWS__)
|
||||
|
||||
# include <winsock2.h>
|
||||
|
@ -317,7 +301,7 @@ template<size_t R>
|
|||
struct BitsetMask<R, R, true>
|
||||
{
|
||||
static const bool correct = true;
|
||||
static const uint32_t value = 1 << R;
|
||||
static const uint32_t value = 1u << R;
|
||||
};
|
||||
|
||||
// This is a trap for a case that BitsetMask::correct in the master template definition
|
||||
|
@ -426,6 +410,88 @@ struct DynamicStruct
|
|||
};
|
||||
|
||||
|
||||
/// Fixed-size array template class.
|
||||
namespace srt {
|
||||
|
||||
template <class T>
|
||||
class FixedArray
|
||||
{
|
||||
public:
|
||||
FixedArray(size_t size)
|
||||
: m_size(size)
|
||||
, m_entries(new T[size])
|
||||
{
|
||||
}
|
||||
|
||||
~FixedArray()
|
||||
{
|
||||
delete [] m_entries;
|
||||
}
|
||||
|
||||
public:
|
||||
const T& operator[](size_t index) const
|
||||
{
|
||||
if (index >= m_size)
|
||||
raise_expection(index);
|
||||
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
T& operator[](size_t index)
|
||||
{
|
||||
if (index >= m_size)
|
||||
raise_expection(index);
|
||||
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
const T& operator[](int index) const
|
||||
{
|
||||
if (index < 0 || static_cast<size_t>(index) >= m_size)
|
||||
raise_expection(index);
|
||||
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
T& operator[](int index)
|
||||
{
|
||||
if (index < 0 || static_cast<size_t>(index) >= m_size)
|
||||
raise_expection(index);
|
||||
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
size_t size() const { return m_size; }
|
||||
|
||||
typedef T* iterator;
|
||||
typedef const T* const_iterator;
|
||||
|
||||
iterator begin() { return m_entries; }
|
||||
iterator end() { return m_entries + m_size; }
|
||||
|
||||
const_iterator cbegin() const { return m_entries; }
|
||||
const_iterator cend() const { return m_entries + m_size; }
|
||||
|
||||
T* data() { return m_entries; }
|
||||
|
||||
private:
|
||||
FixedArray(const FixedArray<T>& );
|
||||
FixedArray<T>& operator=(const FixedArray<T>&);
|
||||
|
||||
void raise_expection(int i) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Index " << i << "out of range";
|
||||
throw std::runtime_error(ss.str());
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_size;
|
||||
T* const m_entries;
|
||||
};
|
||||
|
||||
} // namespace srt
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
|
||||
|
@ -435,47 +501,25 @@ inline bool IsSet(int32_t bitset, int32_t flagset)
|
|||
return (bitset & flagset) == flagset;
|
||||
}
|
||||
|
||||
// Homecooked version of ref_t. It's a copy of std::reference_wrapper
|
||||
// voided of unwanted properties and renamed to ref_t.
|
||||
|
||||
|
||||
#if HAVE_CXX11
|
||||
#include <functional>
|
||||
#endif
|
||||
|
||||
template<typename Type>
|
||||
class ref_t
|
||||
// std::addressof in C++11,
|
||||
// needs to be provided for C++03
|
||||
template <class RefType>
|
||||
inline RefType* AddressOf(RefType& r)
|
||||
{
|
||||
Type* m_data;
|
||||
return (RefType*)(&(unsigned char&)(r));
|
||||
}
|
||||
|
||||
public:
|
||||
typedef Type type;
|
||||
template <class T>
|
||||
struct explicit_t
|
||||
{
|
||||
T inobject;
|
||||
explicit_t(const T& uo): inobject(uo) {}
|
||||
|
||||
#if HAVE_CXX11
|
||||
explicit ref_t(Type& __indata)
|
||||
: m_data(std::addressof(__indata))
|
||||
{ }
|
||||
#else
|
||||
explicit ref_t(Type& __indata)
|
||||
: m_data((Type*)(&(char&)(__indata)))
|
||||
{ }
|
||||
#endif
|
||||
operator T() const { return inobject; }
|
||||
|
||||
ref_t(const ref_t<Type>& inref)
|
||||
: m_data(inref.m_data)
|
||||
{ }
|
||||
|
||||
#if HAVE_CXX11
|
||||
ref_t(const std::reference_wrapper<Type>& i): m_data(std::addressof(i.get())) {}
|
||||
#endif
|
||||
|
||||
Type& operator*() { return *m_data; }
|
||||
|
||||
Type& get() const
|
||||
{ return *m_data; }
|
||||
|
||||
Type operator->() const
|
||||
{ return *m_data; }
|
||||
private:
|
||||
template <class X>
|
||||
explicit_t(const X& another);
|
||||
};
|
||||
|
||||
// This is required for Printable function if you have a container of pairs,
|
||||
|
@ -492,15 +536,6 @@ namespace srt_pair_op
|
|||
|
||||
#if HAVE_CXX11
|
||||
|
||||
// This alias was created so that 'Ref' (not 'ref') is used everywhere.
|
||||
// Normally the C++11 'ref' fits perfectly here, however in C++03 mode
|
||||
// it would have to be newly created. This would then cause a conflict
|
||||
// between C++03 SRT and C++11 applications as well as between C++ standard
|
||||
// library and SRT when SRT is compiled in C++11 mode (as it happens on
|
||||
// Darwin/clang).
|
||||
template <class In>
|
||||
inline auto Ref(In& i) -> decltype(std::ref(i)) { return std::ref(i); }
|
||||
|
||||
template <class In>
|
||||
inline auto Move(In& i) -> decltype(std::move(i)) { return std::move(i); }
|
||||
|
||||
|
@ -530,8 +565,6 @@ inline std::string Sprint(Args&&... args)
|
|||
template <class T>
|
||||
using UniquePtr = std::unique_ptr<T>;
|
||||
|
||||
// Some utilities borrowed from tumux, as this is using options
|
||||
// similar way.
|
||||
template <class Container, class Value = typename Container::value_type, typename... Args> inline
|
||||
std::string Printable(const Container& in, Value /*pseudoargument*/, Args&&... args)
|
||||
{
|
||||
|
@ -577,12 +610,6 @@ auto map_getp(const Map& m, const Key& key) -> typename Map::mapped_type const*
|
|||
|
||||
#else
|
||||
|
||||
template <class Type>
|
||||
ref_t<Type> Ref(Type& arg)
|
||||
{
|
||||
return ref_t<Type>(arg);
|
||||
}
|
||||
|
||||
// The unique_ptr requires C++11, and the rvalue-reference feature,
|
||||
// so here we're simulate the behavior using the old std::auto_ptr.
|
||||
|
||||
|
@ -610,14 +637,14 @@ public:
|
|||
|
||||
// All constructor declarations must be repeated.
|
||||
// "Constructor delegation" is also only C++11 feature.
|
||||
explicit UniquePtr(element_type* __p = 0) throw() : Base(__p) {}
|
||||
UniquePtr(UniquePtr& __a) throw() : Base(__a) { }
|
||||
template<typename _Tp1>
|
||||
UniquePtr(UniquePtr<_Tp1>& __a) throw() : Base(__a) {}
|
||||
explicit UniquePtr(element_type* p = 0) throw() : Base(p) {}
|
||||
UniquePtr(UniquePtr& a) throw() : Base(a) { }
|
||||
template<typename Type1>
|
||||
UniquePtr(UniquePtr<Type1>& a) throw() : Base(a) {}
|
||||
|
||||
UniquePtr& operator=(UniquePtr& __a) throw() { return Base::operator=(__a); }
|
||||
template<typename _Tp1>
|
||||
UniquePtr& operator=(UniquePtr<_Tp1>& __a) throw() { return Base::operator=(__a); }
|
||||
UniquePtr& operator=(UniquePtr& a) throw() { return Base::operator=(a); }
|
||||
template<typename Type1>
|
||||
UniquePtr& operator=(UniquePtr<Type1>& a) throw() { return Base::operator=(a); }
|
||||
|
||||
// Good, now we need to add some parts of the API of unique_ptr.
|
||||
|
||||
|
@ -630,7 +657,15 @@ public:
|
|||
operator bool () { return 0!= get(); }
|
||||
};
|
||||
|
||||
// A primitive one-argument version of Printable
|
||||
// A primitive one-argument versions of Sprint and Printable
|
||||
template <class Arg1>
|
||||
inline std::string Sprint(const Arg1& arg)
|
||||
{
|
||||
std::ostringstream sout;
|
||||
sout << arg;
|
||||
return sout.str();
|
||||
}
|
||||
|
||||
template <class Container> inline
|
||||
std::string Printable(const Container& in)
|
||||
{
|
||||
|
@ -675,6 +710,44 @@ typename Map::mapped_type const* map_getp(const Map& m, const Key& key)
|
|||
|
||||
#endif
|
||||
|
||||
// Printable with prefix added for every element.
|
||||
// Useful when printing a container of sockets or sequence numbers.
|
||||
template <class Container> inline
|
||||
std::string PrintableMod(const Container& in, const std::string& prefix)
|
||||
{
|
||||
using namespace srt_pair_op;
|
||||
typedef typename Container::value_type Value;
|
||||
std::ostringstream os;
|
||||
os << "[ ";
|
||||
for (typename Container::const_iterator y = in.begin(); y != in.end(); ++y)
|
||||
os << prefix << Value(*y) << " ";
|
||||
os << "]";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
template<typename InputIterator, typename OutputIterator, typename TransFunction>
|
||||
inline void FilterIf(InputIterator bg, InputIterator nd,
|
||||
OutputIterator out, TransFunction fn)
|
||||
{
|
||||
for (InputIterator i = bg; i != nd; ++i)
|
||||
{
|
||||
std::pair<typename TransFunction::result_type, bool> result = fn(*i);
|
||||
if (!result.second)
|
||||
continue;
|
||||
*out++ = result.first;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Value, class ArgValue>
|
||||
inline void insert_uniq(std::vector<Value>& v, const ArgValue& val)
|
||||
{
|
||||
typename std::vector<Value>::iterator i = std::find(v.begin(), v.end(), val);
|
||||
if (i != v.end())
|
||||
return;
|
||||
|
||||
v.push_back(val);
|
||||
}
|
||||
|
||||
template <class Signature>
|
||||
struct CallbackHolder
|
||||
{
|
||||
|
@ -696,7 +769,8 @@ struct CallbackHolder
|
|||
// Casting function-to-function, however, should not. Unfortunately
|
||||
// newer compilers disallow that, too (when a signature differs), but
|
||||
// then they should better use the C++11 way, much more reliable and safer.
|
||||
void* (*testfn)(void*) ATR_UNUSED = (void*(*)(void*))f;
|
||||
void* (*testfn)(void*) = (void*(*)(void*))f;
|
||||
(void)(testfn);
|
||||
#endif
|
||||
opaque = o;
|
||||
fn = f;
|
||||
|
@ -767,11 +841,13 @@ public:
|
|||
m_qDriftSum += driftval;
|
||||
++m_uDriftSpan;
|
||||
|
||||
// I moved it here to calculate accumulated overdrift.
|
||||
if (CLEAR_ON_UPDATE)
|
||||
m_qOverdrift = 0;
|
||||
|
||||
if (m_uDriftSpan < MAX_SPAN)
|
||||
return false;
|
||||
|
||||
if (CLEAR_ON_UPDATE)
|
||||
m_qOverdrift = 0;
|
||||
|
||||
// Calculate the median of all drift values.
|
||||
// In most cases, the divisor should be == MAX_SPAN.
|
||||
|
@ -799,6 +875,12 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
// For group overrides
|
||||
void forceDrift(int64_t driftval)
|
||||
{
|
||||
m_qDrift = driftval;
|
||||
}
|
||||
|
||||
// These values can be read at any time, however if you want
|
||||
// to depend on the fact that they have been changed lately,
|
||||
// you have to check the return value from update().
|
||||
|
@ -869,15 +951,15 @@ struct MapProxy
|
|||
}
|
||||
};
|
||||
|
||||
/// Print some hash-based stamp of the first 16 bytes in the buffer
|
||||
inline std::string BufferStamp(const char* mem, size_t size)
|
||||
{
|
||||
using namespace std;
|
||||
char spread[16];
|
||||
|
||||
int n = 16-size;
|
||||
if (n > 0)
|
||||
memset(spread+16-n, 0, n);
|
||||
memcpy(spread, mem, min(size_t(16), size));
|
||||
if (size < 16)
|
||||
memset((spread + size), 0, 16 - size);
|
||||
memcpy((spread), mem, min(size_t(16), size));
|
||||
|
||||
// Now prepare 4 cells for uint32_t.
|
||||
union
|
||||
|
@ -885,7 +967,7 @@ inline std::string BufferStamp(const char* mem, size_t size)
|
|||
uint32_t sum;
|
||||
char cells[4];
|
||||
};
|
||||
memset(cells, 0, 4);
|
||||
memset((cells), 0, 4);
|
||||
|
||||
for (size_t x = 0; x < 4; ++x)
|
||||
for (size_t y = 0; y < 4; ++y)
|
||||
|
@ -894,9 +976,7 @@ inline std::string BufferStamp(const char* mem, size_t size)
|
|||
}
|
||||
|
||||
// Convert to hex string
|
||||
|
||||
ostringstream os;
|
||||
|
||||
os << hex << uppercase << setfill('0') << setw(8) << sum;
|
||||
|
||||
return os.str();
|
||||
|
@ -963,7 +1043,56 @@ ATR_CONSTEXPR size_t Size(const V (&)[N]) ATR_NOEXCEPT { return N; }
|
|||
template <size_t DEPRLEN, typename ValueType>
|
||||
inline ValueType avg_iir(ValueType old_value, ValueType new_value)
|
||||
{
|
||||
return (old_value*(DEPRLEN-1) + new_value)/DEPRLEN;
|
||||
return (old_value * (DEPRLEN - 1) + new_value) / DEPRLEN;
|
||||
}
|
||||
|
||||
template <size_t DEPRLEN, typename ValueType>
|
||||
inline ValueType avg_iir_w(ValueType old_value, ValueType new_value, size_t new_val_weight)
|
||||
{
|
||||
return (old_value * (DEPRLEN - new_val_weight) + new_value * new_val_weight) / DEPRLEN;
|
||||
}
|
||||
|
||||
// Property accessor definitions
|
||||
//
|
||||
// "Property" is a special method that accesses given field.
|
||||
// This relies only on a convention, which is the following:
|
||||
//
|
||||
// V x = object.prop(); <-- get the property's value
|
||||
// object.prop(x); <-- set the property a value
|
||||
//
|
||||
// Properties might be also chained when setting:
|
||||
//
|
||||
// object.prop1(v1).prop2(v2).prop3(v3);
|
||||
//
|
||||
// Properties may be defined various even very complicated
|
||||
// ways, which is simply providing a method with body. In order
|
||||
// to define a property simplest possible way, that is, refer
|
||||
// directly to the field that keeps it, here are the following macros:
|
||||
//
|
||||
// Prefix: SRTU_PROPERTY_
|
||||
// Followed by:
|
||||
// - access type: RO, WO, RW, RR, RRW
|
||||
// - chain flag: optional _CHAIN
|
||||
// Where access type is:
|
||||
// - RO - read only. Defines reader accessor. The accessor method will be const.
|
||||
// - RR - read reference. The accessor isn't const to allow reference passthrough.
|
||||
// - WO - write only. Defines writer accessor.
|
||||
// - RW - combines RO and WO.
|
||||
// - RRW - combines RR and WO.
|
||||
//
|
||||
// The _CHAIN marker is optional for macros providing writable accessors
|
||||
// for properties. The difference is that while simple write accessors return
|
||||
// void, the chaining accessors return the reference to the object for which
|
||||
// the write accessor was called so that you can call the next accessor (or
|
||||
// any other method as well) for the result.
|
||||
|
||||
#define SRTU_PROPERTY_RR(type, name, field) type name() { return field; }
|
||||
#define SRTU_PROPERTY_RO(type, name, field) type name() const { return field; }
|
||||
#define SRTU_PROPERTY_WO(type, name, field) void set_##name(type arg) { field = arg; }
|
||||
#define SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) otype& set_##name(type arg) { field = arg; return *this; }
|
||||
#define SRTU_PROPERTY_RW(type, name, field) SRTU_PROPERTY_RO(type, name, field); SRTU_PROPERTY_WO(type, name, field)
|
||||
#define SRTU_PROPERTY_RRW(type, name, field) SRTU_PROPERTY_RR(type, name, field); SRTU_PROPERTY_WO(type, name, field)
|
||||
#define SRTU_PROPERTY_RW_CHAIN(otype, type, name, field) SRTU_PROPERTY_RO(type, name, field); SRTU_PROPERTY_WO_CHAIN(otype, type, name, field)
|
||||
#define SRTU_PROPERTY_RRW_CHAIN(otype, type, name, field) SRTU_PROPERTY_RR(type, name, field); SRTU_PROPERTY_WO_CHAIN(otype, type, name, field)
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue