Merge branch 'master' into vulkan
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/math.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
@@ -45,12 +46,25 @@ void Arena::DebugFill() {
|
||||
}
|
||||
}
|
||||
|
||||
void* Arena::Alloc(size_t size) {
|
||||
void* Arena::Alloc(size_t size, size_t align) {
|
||||
assert_true(
|
||||
xe::bit_count(align) == 1 && align <= 16,
|
||||
"align needs to be a power of 2 and not greater than Chunk alignment");
|
||||
|
||||
// for alignment
|
||||
const auto get_padding = [this, align]() -> size_t {
|
||||
const size_t mask = align - 1;
|
||||
size_t deviation = active_chunk_->offset & mask;
|
||||
return (align - deviation) & mask;
|
||||
};
|
||||
|
||||
if (active_chunk_) {
|
||||
if (active_chunk_->capacity - active_chunk_->offset < size + 4096) {
|
||||
if (active_chunk_->capacity - active_chunk_->offset <
|
||||
size + get_padding() + 4096) {
|
||||
Chunk* next = active_chunk_->next;
|
||||
if (!next) {
|
||||
assert_true(size < chunk_size_, "need to support larger chunks");
|
||||
assert_true(size + get_padding() < chunk_size_,
|
||||
"need to support larger chunks");
|
||||
next = new Chunk(chunk_size_);
|
||||
active_chunk_->next = next;
|
||||
}
|
||||
@@ -61,8 +75,11 @@ void* Arena::Alloc(size_t size) {
|
||||
head_chunk_ = active_chunk_ = new Chunk(chunk_size_);
|
||||
}
|
||||
|
||||
active_chunk_->offset += get_padding();
|
||||
uint8_t* p = active_chunk_->buffer + active_chunk_->offset;
|
||||
active_chunk_->offset += size;
|
||||
assert_true((reinterpret_cast<size_t>(p) & (align - 1)) == 0,
|
||||
"alignment failed");
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -113,6 +130,8 @@ void Arena::CloneContents(void* buffer, size_t buffer_length) {
|
||||
Arena::Chunk::Chunk(size_t chunk_size)
|
||||
: next(nullptr), capacity(chunk_size), buffer(0), offset(0) {
|
||||
buffer = reinterpret_cast<uint8_t*>(malloc(capacity));
|
||||
assert_true((reinterpret_cast<size_t>(buffer) & size_t(15)) == 0,
|
||||
"16 byte alignment required");
|
||||
}
|
||||
|
||||
Arena::Chunk::~Chunk() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -24,11 +24,13 @@ class Arena {
|
||||
void Reset();
|
||||
void DebugFill();
|
||||
|
||||
void* Alloc(size_t size);
|
||||
void* Alloc(size_t size, size_t align);
|
||||
template <typename T>
|
||||
T* Alloc() {
|
||||
return reinterpret_cast<T*>(Alloc(sizeof(T)));
|
||||
return reinterpret_cast<T*>(Alloc(sizeof(T), alignof(T)));
|
||||
}
|
||||
// When rewinding aligned allocations, any padding that was applied during
|
||||
// allocation will be leaked
|
||||
void Rewind(size_t size);
|
||||
|
||||
void* CloneContents();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -111,6 +111,8 @@ size_t BitStream::Copy(uint8_t* dest_buffer, size_t num_bits) {
|
||||
// First: Copy the first few bits up to a byte boundary.
|
||||
if (rel_offset_bits) {
|
||||
uint64_t bits = Peek(8 - rel_offset_bits);
|
||||
uint8_t clear_mask = ~((uint8_t(1) << rel_offset_bits) - 1);
|
||||
dest_buffer[out_offset_bytes] &= clear_mask;
|
||||
dest_buffer[out_offset_bytes] |= (uint8_t)bits;
|
||||
|
||||
bits_left -= 8 - rel_offset_bits;
|
||||
@@ -132,6 +134,8 @@ size_t BitStream::Copy(uint8_t* dest_buffer, size_t num_bits) {
|
||||
uint64_t bits = Peek(bits_left);
|
||||
bits <<= 8 - bits_left;
|
||||
|
||||
uint8_t clear_mask = ((uint8_t(1) << bits_left) - 1);
|
||||
dest_buffer[out_offset_bytes] &= clear_mask;
|
||||
dest_buffer[out_offset_bytes] |= (uint8_t)bits;
|
||||
Advance(bits_left);
|
||||
}
|
||||
|
||||
@@ -11,103 +11,113 @@
|
||||
#define XENIA_BASE_BYTE_ORDER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#if defined __has_include
|
||||
#if __has_include(<version>)
|
||||
#include <version>
|
||||
#endif
|
||||
#endif
|
||||
#if __cpp_lib_endian
|
||||
#include <bit>
|
||||
#endif
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#if XE_PLATFORM_LINUX
|
||||
#include <byteswap.h>
|
||||
#if !__cpp_lib_endian
|
||||
// Polyfill
|
||||
#ifdef __BYTE_ORDER__
|
||||
namespace std {
|
||||
enum class endian {
|
||||
little = __ORDER_LITTLE_ENDIAN__,
|
||||
big = __ORDER_BIG_ENDIAN__,
|
||||
native = __BYTE_ORDER__
|
||||
};
|
||||
}
|
||||
#else
|
||||
// Hardcode to little endian for now
|
||||
namespace std {
|
||||
enum class endian { little = 0, big = 1, native = 0 };
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
// Check for mixed endian
|
||||
static_assert((std::endian::native == std::endian::big) ||
|
||||
(std::endian::native == std::endian::little));
|
||||
|
||||
namespace xe {
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
#if XE_COMPILER_MSVC
|
||||
#define XENIA_BASE_BYTE_SWAP_16 _byteswap_ushort
|
||||
#define XENIA_BASE_BYTE_SWAP_32 _byteswap_ulong
|
||||
#define XENIA_BASE_BYTE_SWAP_64 _byteswap_uint64
|
||||
#elif XE_PLATFORM_MAC
|
||||
#define XENIA_BASE_BYTE_SWAP_16 OSSwapInt16
|
||||
#define XENIA_BASE_BYTE_SWAP_32 OSSwapInt32
|
||||
#define XENIA_BASE_BYTE_SWAP_64 OSSwapInt64
|
||||
#else
|
||||
#define XENIA_BASE_BYTE_SWAP_16 bswap_16
|
||||
#define XENIA_BASE_BYTE_SWAP_32 bswap_32
|
||||
#define XENIA_BASE_BYTE_SWAP_64 bswap_64
|
||||
#define XENIA_BASE_BYTE_SWAP_16 __builtin_bswap16
|
||||
#define XENIA_BASE_BYTE_SWAP_32 __builtin_bswap32
|
||||
#define XENIA_BASE_BYTE_SWAP_64 __builtin_bswap64
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
inline int8_t byte_swap(int8_t value) { return value; }
|
||||
inline uint8_t byte_swap(uint8_t value) { return value; }
|
||||
inline int16_t byte_swap(int16_t value) {
|
||||
return static_cast<int16_t>(
|
||||
XENIA_BASE_BYTE_SWAP_16(static_cast<int16_t>(value)));
|
||||
}
|
||||
inline uint16_t byte_swap(uint16_t value) {
|
||||
return XENIA_BASE_BYTE_SWAP_16(value);
|
||||
}
|
||||
inline uint16_t byte_swap(char16_t value) {
|
||||
return static_cast<char16_t>(XENIA_BASE_BYTE_SWAP_16(value));
|
||||
}
|
||||
inline int32_t byte_swap(int32_t value) {
|
||||
return static_cast<int32_t>(
|
||||
XENIA_BASE_BYTE_SWAP_32(static_cast<int32_t>(value)));
|
||||
}
|
||||
inline uint32_t byte_swap(uint32_t value) {
|
||||
return XENIA_BASE_BYTE_SWAP_32(value);
|
||||
}
|
||||
inline int64_t byte_swap(int64_t value) {
|
||||
return static_cast<int64_t>(
|
||||
XENIA_BASE_BYTE_SWAP_64(static_cast<int64_t>(value)));
|
||||
}
|
||||
inline uint64_t byte_swap(uint64_t value) {
|
||||
return XENIA_BASE_BYTE_SWAP_64(value);
|
||||
}
|
||||
inline float byte_swap(float value) {
|
||||
uint32_t temp = byte_swap(*reinterpret_cast<uint32_t*>(&value));
|
||||
return *reinterpret_cast<float*>(&temp);
|
||||
}
|
||||
inline double byte_swap(double value) {
|
||||
uint64_t temp = byte_swap(*reinterpret_cast<uint64_t*>(&value));
|
||||
return *reinterpret_cast<double*>(&temp);
|
||||
}
|
||||
template <typename T>
|
||||
template <class T>
|
||||
inline T byte_swap(T value) {
|
||||
if (sizeof(T) == 4) {
|
||||
return static_cast<T>(byte_swap(static_cast<uint32_t>(value)));
|
||||
} else if (sizeof(T) == 2) {
|
||||
return static_cast<T>(byte_swap(static_cast<uint16_t>(value)));
|
||||
} else {
|
||||
assert_always("not handled");
|
||||
static_assert(
|
||||
sizeof(T) == 8 || sizeof(T) == 4 || sizeof(T) == 2 || sizeof(T) == 1,
|
||||
"byte_swap(T value): Type T has illegal size");
|
||||
if constexpr (sizeof(T) == 8) {
|
||||
uint64_t temp =
|
||||
XENIA_BASE_BYTE_SWAP_64(*reinterpret_cast<uint64_t*>(&value));
|
||||
return *reinterpret_cast<T*>(&temp);
|
||||
} else if constexpr (sizeof(T) == 4) {
|
||||
uint32_t temp =
|
||||
XENIA_BASE_BYTE_SWAP_32(*reinterpret_cast<uint32_t*>(&value));
|
||||
return *reinterpret_cast<T*>(&temp);
|
||||
} else if constexpr (sizeof(T) == 2) {
|
||||
uint16_t temp =
|
||||
XENIA_BASE_BYTE_SWAP_16(*reinterpret_cast<uint16_t*>(&value));
|
||||
return *reinterpret_cast<T*>(&temp);
|
||||
} else if constexpr (sizeof(T) == 1) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct be {
|
||||
be() = default;
|
||||
be(const T& src) : value(xe::byte_swap(src)) {} // NOLINT(runtime/explicit)
|
||||
be(const be& other) { value = other.value; } // NOLINT(runtime/explicit)
|
||||
operator T() const { return xe::byte_swap(value); }
|
||||
template <typename T, std::endian E>
|
||||
struct endian_store {
|
||||
endian_store() = default;
|
||||
endian_store(const T& src) {
|
||||
if constexpr (std::endian::native == E) {
|
||||
value = src;
|
||||
} else {
|
||||
value = xe::byte_swap(src);
|
||||
}
|
||||
}
|
||||
endian_store(const endian_store& other) { value = other.value; }
|
||||
operator T() const {
|
||||
if constexpr (std::endian::native == E) {
|
||||
return value;
|
||||
} else {
|
||||
return xe::byte_swap(value);
|
||||
}
|
||||
}
|
||||
|
||||
be<T>& operator+=(int a) {
|
||||
endian_store<T, E>& operator+=(int a) {
|
||||
*this = *this + a;
|
||||
return *this;
|
||||
}
|
||||
be<T>& operator-=(int a) {
|
||||
endian_store<T, E>& operator-=(int a) {
|
||||
*this = *this - a;
|
||||
return *this;
|
||||
}
|
||||
be<T>& operator++() {
|
||||
endian_store<T, E>& operator++() {
|
||||
*this += 1;
|
||||
return *this;
|
||||
} // ++a
|
||||
be<T> operator++(int) {
|
||||
endian_store<T, E> operator++(int) {
|
||||
*this += 1;
|
||||
return (*this - 1);
|
||||
} // a++
|
||||
be<T>& operator--() {
|
||||
endian_store<T, E>& operator--() {
|
||||
*this -= 1;
|
||||
return *this;
|
||||
} // --a
|
||||
be<T> operator--(int) {
|
||||
endian_store<T, E> operator--(int) {
|
||||
*this -= 1;
|
||||
return (*this + 1);
|
||||
} // a--
|
||||
@@ -115,6 +125,11 @@ struct be {
|
||||
T value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using be = endian_store<T, std::endian::big>;
|
||||
template <typename T>
|
||||
using le = endian_store<T, std::endian::little>;
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_BYTE_ORDER_H_
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace cvar {
|
||||
cxxopts::Options options("xenia", "Xbox 360 Emulator");
|
||||
std::map<std::string, ICommandVar*>* CmdVars;
|
||||
std::map<std::string, IConfigVar*>* ConfigVars;
|
||||
std::multimap<uint32_t, const IConfigVarUpdate*>* IConfigVarUpdate::updates_;
|
||||
|
||||
void PrintHelpAndExit() {
|
||||
std::cout << options.help({""}) << std::endl;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "cpptoml/include/cpptoml.h"
|
||||
#include "cxxopts/include/cxxopts.hpp"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
|
||||
@@ -43,6 +44,7 @@ class IConfigVar : virtual public ICommandVar {
|
||||
virtual std::string config_value() const = 0;
|
||||
virtual void LoadConfigValue(std::shared_ptr<cpptoml::base> result) = 0;
|
||||
virtual void LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) = 0;
|
||||
virtual void ResetConfigValueToDefault() = 0;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
@@ -75,6 +77,7 @@ class ConfigVar : public CommandVar<T>, virtual public IConfigVar {
|
||||
ConfigVar<T>(const char* name, T* default_value, const char* description,
|
||||
const char* category, bool is_transient);
|
||||
std::string config_value() const override;
|
||||
const T& GetTypedConfigValue() const;
|
||||
const std::string& category() const override;
|
||||
bool is_transient() const override;
|
||||
void AddToLaunchOptions(cxxopts::Options* options) override;
|
||||
@@ -89,6 +92,7 @@ class ConfigVar : public CommandVar<T>, virtual public IConfigVar {
|
||||
std::unique_ptr<T> config_value_ = nullptr;
|
||||
std::unique_ptr<T> game_config_value_ = nullptr;
|
||||
void UpdateValue() override;
|
||||
void ResetConfigValueToDefault() override;
|
||||
};
|
||||
|
||||
#pragma warning(pop)
|
||||
@@ -233,6 +237,10 @@ std::string ConfigVar<T>::config_value() const {
|
||||
return this->ToString(this->default_value_);
|
||||
}
|
||||
template <class T>
|
||||
const T& ConfigVar<T>::GetTypedConfigValue() const {
|
||||
return config_value_ ? *config_value_ : this->default_value_;
|
||||
}
|
||||
template <class T>
|
||||
void CommandVar<T>::SetCommandLineValue(const T val) {
|
||||
commandline_value_ = std::make_unique<T>(val);
|
||||
UpdateValue();
|
||||
@@ -247,36 +255,47 @@ void ConfigVar<T>::SetGameConfigValue(T val) {
|
||||
game_config_value_ = std::make_unique<T>(val);
|
||||
UpdateValue();
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::ResetConfigValueToDefault() {
|
||||
SetConfigValue(this->default_value_);
|
||||
}
|
||||
|
||||
// CVars can be initialized before these, thus initialized on-demand using new.
|
||||
extern std::map<std::string, ICommandVar*>* CmdVars;
|
||||
extern std::map<std::string, IConfigVar*>* ConfigVars;
|
||||
|
||||
inline void AddConfigVar(IConfigVar* cv) {
|
||||
if (!ConfigVars) ConfigVars = new std::map<std::string, IConfigVar*>();
|
||||
ConfigVars->insert(std::pair<std::string, IConfigVar*>(cv->name(), cv));
|
||||
if (!ConfigVars) {
|
||||
ConfigVars = new std::map<std::string, IConfigVar*>;
|
||||
}
|
||||
ConfigVars->emplace(cv->name(), cv);
|
||||
}
|
||||
inline void AddCommandVar(ICommandVar* cv) {
|
||||
if (!CmdVars) CmdVars = new std::map<std::string, ICommandVar*>();
|
||||
CmdVars->insert(std::pair<std::string, ICommandVar*>(cv->name(), cv));
|
||||
if (!CmdVars) {
|
||||
CmdVars = new std::map<std::string, ICommandVar*>;
|
||||
}
|
||||
CmdVars->emplace(cv->name(), cv);
|
||||
}
|
||||
void ParseLaunchArguments(int& argc, char**& argv,
|
||||
const std::string_view positional_help,
|
||||
const std::vector<std::string>& positional_options);
|
||||
|
||||
template <typename T>
|
||||
T* define_configvar(const char* name, T* default_value, const char* description,
|
||||
const char* category, bool is_transient) {
|
||||
IConfigVar* cfgVar = new ConfigVar<T>(name, default_value, description,
|
||||
IConfigVar* define_configvar(const char* name, T* default_value,
|
||||
const char* description, const char* category,
|
||||
bool is_transient) {
|
||||
IConfigVar* cfgvar = new ConfigVar<T>(name, default_value, description,
|
||||
category, is_transient);
|
||||
AddConfigVar(cfgVar);
|
||||
return default_value;
|
||||
AddConfigVar(cfgvar);
|
||||
return cfgvar;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||
ICommandVar* cmdVar = new CommandVar<T>(name, default_value, description);
|
||||
AddCommandVar(cmdVar);
|
||||
return default_value;
|
||||
ICommandVar* define_cmdvar(const char* name, T* default_value,
|
||||
const char* description) {
|
||||
ICommandVar* cmdvar = new CommandVar<T>(name, default_value, description);
|
||||
AddCommandVar(cmdvar);
|
||||
return cmdvar;
|
||||
}
|
||||
|
||||
#define DEFINE_bool(name, default_value, description, category) \
|
||||
@@ -285,6 +304,9 @@ T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||
#define DEFINE_int32(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, int32_t)
|
||||
|
||||
#define DEFINE_uint32(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, uint32_t)
|
||||
|
||||
#define DEFINE_uint64(name, default_value, description, category) \
|
||||
DEFINE_CVar(name, default_value, description, category, false, uint64_t)
|
||||
|
||||
@@ -314,7 +336,7 @@ T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||
type name = default_value; \
|
||||
} \
|
||||
namespace cv { \
|
||||
static auto cv_##name = cvar::define_configvar( \
|
||||
static cvar::IConfigVar* const cv_##name = cvar::define_configvar( \
|
||||
#name, &cvars::name, description, category, is_transient); \
|
||||
}
|
||||
|
||||
@@ -324,7 +346,7 @@ T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||
std::string name = default_value; \
|
||||
} \
|
||||
namespace cv { \
|
||||
static auto cv_##name = \
|
||||
static cvar::ICommandVar* const cv_##name = \
|
||||
cvar::define_cmdvar(#name, &cvars::name, description); \
|
||||
}
|
||||
|
||||
@@ -332,6 +354,8 @@ T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||
|
||||
#define DECLARE_int32(name) DECLARE_CVar(name, int32_t)
|
||||
|
||||
#define DECLARE_uint32(name) DECLARE_CVar(name, uint32_t)
|
||||
|
||||
#define DECLARE_uint64(name) DECLARE_CVar(name, uint64_t)
|
||||
|
||||
#define DECLARE_double(name) DECLARE_CVar(name, double)
|
||||
@@ -345,6 +369,212 @@ T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||
extern type name; \
|
||||
}
|
||||
|
||||
// Interface for changing the default value of a variable with auto-upgrading of
|
||||
// users' configs (to distinguish between a leftover old default and an explicit
|
||||
// override), without having to rename the variable.
|
||||
//
|
||||
// Two types of updates are supported:
|
||||
// - Changing the value of the variable (UPDATE_from_type) from an explicitly
|
||||
// specified previous default value to a new one, but keeping the
|
||||
// user-specified value if it was not the default, and thus explicitly
|
||||
// overridden.
|
||||
// - Changing the meaning / domain of the variable (UPDATE_from_any), when
|
||||
// previous user-specified overrides also stop making sense. Config variable
|
||||
// type changes are also considered this type of updates (though
|
||||
// UPDATE_from_type, if the new type doesn't match the previous one, is also
|
||||
// safe to use - it behaves like UPDATE_from_any in this case).
|
||||
//
|
||||
// Rules of using UPDATE_:
|
||||
// - Do not remove previous UPDATE_ entries (both typed and from-any) if you're
|
||||
// adding a new UPDATE_from_type.
|
||||
// This ensures that if the default was changed from 1 to 2 and then to 3,
|
||||
// both users who last launched Xenia when it was 1 and when it was 2 receive
|
||||
// the update (however, those who have explicitly changed it from 2 to 1 when
|
||||
// 2 was the default will have it kept at 1).
|
||||
// It's safe to remove the history before a new UPDATE_from_any, however.
|
||||
// - The date should preferably be in UTC+0 timezone.
|
||||
// - No other pull recent pull requests should have the same date (since builds
|
||||
// are made after every commit).
|
||||
// - IConfigVarUpdate::kLastCommittedUpdateDate must be updated - see the
|
||||
// comment near its declaration.
|
||||
|
||||
constexpr uint32_t MakeConfigVarUpdateDate(uint32_t year, uint32_t month,
|
||||
uint32_t day, uint32_t utc_hour) {
|
||||
// Written to the config as a decimal number - pack as decimal for user
|
||||
// readability.
|
||||
// Using 31 bits in the 3rd millennium already - don't add more digits.
|
||||
return utc_hour + day * 100 + month * 10000 + year * 1000000;
|
||||
}
|
||||
|
||||
class IConfigVarUpdate {
|
||||
public:
|
||||
// This global highest version constant is used to ensure that version (which
|
||||
// is stored as one value for the whole config file) is monotonically
|
||||
// increased when commits - primarily pull requests - are pushed to the main
|
||||
// branch.
|
||||
//
|
||||
// This is to prevent the following situation:
|
||||
// - Pull request #1 created on day 1.
|
||||
// - Pull request #2 created on day 2.
|
||||
// - Pull request #2 from day 2 merged on day 3.
|
||||
// - User launches the latest version on day 4.
|
||||
// CVar default changes from PR #2 (day 2) applied because the user's config
|
||||
// version is day 0, which is < 2.
|
||||
// User's config has day 2 version now.
|
||||
// - Pull request #1 from day 1 merged on day 5.
|
||||
// - User launches the latest version on day 5.
|
||||
// CVar default changes from PR #1 (day 1) IGNORED because the user's config
|
||||
// version is day 2, which is >= 1.
|
||||
//
|
||||
// If this constant is not updated, static_assert will be triggered for a new
|
||||
// DEFINE_, requiring this constant to be raised. But changing this will
|
||||
// result in merge conflicts in all other pull requests also changing cvar
|
||||
// defaults - before they're merged, they will need to be updated, which will
|
||||
// ensure monotonic growth of the versions of all cvars on the main branch. In
|
||||
// the example above, PR #1 will need to be updated before it's merged.
|
||||
//
|
||||
// If you've encountered a merge conflict here in your pull request:
|
||||
// 1) Update any UPDATE_s you've added in the pull request to the current
|
||||
// date.
|
||||
// 2) Change this value to the same date.
|
||||
// If you're reviewing a pull request with a change here, check if 1) has been
|
||||
// done by the submitter before merging.
|
||||
static constexpr uint32_t kLastCommittedUpdateDate =
|
||||
MakeConfigVarUpdateDate(2020, 12, 31, 13);
|
||||
|
||||
virtual ~IConfigVarUpdate() = default;
|
||||
|
||||
virtual void Apply() const = 0;
|
||||
|
||||
static void ApplyUpdates(uint32_t config_date) {
|
||||
if (!updates_) {
|
||||
return;
|
||||
}
|
||||
auto it_end = updates_->end();
|
||||
for (auto it = updates_->upper_bound(config_date); it != it_end; ++it) {
|
||||
it->second->Apply();
|
||||
}
|
||||
}
|
||||
|
||||
// More reliable than kLastCommittedUpdateDate for actual usage
|
||||
// (kLastCommittedUpdateDate is just a pull request merge order guard), though
|
||||
// usually should be the same, but kLastCommittedUpdateDate may not include
|
||||
// removal of cvars.
|
||||
static uint32_t GetLastUpdateDate() {
|
||||
return (updates_ && !updates_->empty()) ? updates_->crbegin()->first : 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
IConfigVarUpdate(IConfigVar* const& config_var, uint32_t year, uint32_t month,
|
||||
uint32_t day, uint32_t utc_hour)
|
||||
: config_var_(config_var) {
|
||||
if (!updates_) {
|
||||
updates_ = new std::multimap<uint32_t, const IConfigVarUpdate*>;
|
||||
}
|
||||
updates_->emplace(MakeConfigVarUpdateDate(year, month, day, utc_hour),
|
||||
this);
|
||||
}
|
||||
|
||||
IConfigVar& config_var() const {
|
||||
assert_not_null(config_var_);
|
||||
return *config_var_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Reference to pointer to loosen initialization order requirements.
|
||||
IConfigVar* const& config_var_;
|
||||
|
||||
// Updates can be initialized before these, thus initialized on demand using
|
||||
// `new`.
|
||||
static std::multimap<uint32_t, const IConfigVarUpdate*>* updates_;
|
||||
};
|
||||
|
||||
class ConfigVarUpdateFromAny : public IConfigVarUpdate {
|
||||
public:
|
||||
ConfigVarUpdateFromAny(IConfigVar* const& config_var, uint32_t year,
|
||||
uint32_t month, uint32_t day, uint32_t utc_hour)
|
||||
: IConfigVarUpdate(config_var, year, month, day, utc_hour) {}
|
||||
void Apply() const override { config_var().ResetConfigValueToDefault(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ConfigVarUpdate : public IConfigVarUpdate {
|
||||
public:
|
||||
ConfigVarUpdate(IConfigVar* const& config_var, uint32_t year, uint32_t month,
|
||||
uint32_t day, uint32_t utc_hour, const T& old_default_value)
|
||||
: IConfigVarUpdate(config_var, year, month, day, utc_hour),
|
||||
old_default_value_(old_default_value) {}
|
||||
void Apply() const override {
|
||||
IConfigVar& config_var_untyped = config_var();
|
||||
ConfigVar<T>* config_var_typed =
|
||||
dynamic_cast<ConfigVar<T>*>(&config_var_untyped);
|
||||
// Update only from the previous default value if the same type,
|
||||
// unconditionally reset if the type has been changed.
|
||||
if (!config_var_typed ||
|
||||
config_var_typed->GetTypedConfigValue() == old_default_value_) {
|
||||
config_var_untyped.ResetConfigValueToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
T old_default_value_;
|
||||
};
|
||||
|
||||
#define UPDATE_from_any(name, year, month, day, utc_hour) \
|
||||
static_assert( \
|
||||
cvar::MakeConfigVarUpdateDate(year, month, day, utc_hour) <= \
|
||||
cvar::IConfigVarUpdate::kLastCommittedUpdateDate, \
|
||||
"A new config variable default value update was added - raise " \
|
||||
"cvar::IConfigVarUpdate::kLastCommittedUpdateDate to the same date in " \
|
||||
"base/cvar.h to ensure coherence between different pull requests " \
|
||||
"updating config variable defaults."); \
|
||||
namespace cv { \
|
||||
static const cvar::ConfigVarUpdateFromAny \
|
||||
update_##name_##year_##month_##day_##utc_hour(cv_##name, year, month, \
|
||||
day, utc_hour); \
|
||||
}
|
||||
|
||||
#define UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, type) \
|
||||
static_assert( \
|
||||
cvar::MakeConfigVarUpdateDate(year, month, day, utc_hour) <= \
|
||||
cvar::IConfigVarUpdate::kLastCommittedUpdateDate, \
|
||||
"A new config variable default value update was added - raise " \
|
||||
"cvar::IConfigVarUpdate::kLastCommittedUpdateDate to the same date in " \
|
||||
"base/cvar.h to ensure coherence between different pull requests " \
|
||||
"updating config variable defaults."); \
|
||||
namespace cv { \
|
||||
static const cvar::ConfigVarUpdate<type> \
|
||||
update_##name_##year_##month_##day_##utc_hour(cv_##name, year, month, \
|
||||
day, utc_hour, \
|
||||
old_default_value); \
|
||||
}
|
||||
|
||||
#define UPDATE_from_bool(name, year, month, day, utc_hour, old_default_value) \
|
||||
UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, bool)
|
||||
|
||||
#define UPDATE_from_int32(name, year, month, day, utc_hour, old_default_value) \
|
||||
UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, int32_t)
|
||||
|
||||
#define UPDATE_from_uint32(name, year, month, day, utc_hour, \
|
||||
old_default_value) \
|
||||
UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, uint32_t)
|
||||
|
||||
#define UPDATE_from_uint64(name, year, month, day, utc_hour, \
|
||||
old_default_value) \
|
||||
UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, uint64_t)
|
||||
|
||||
#define UPDATE_from_double(name, year, month, day, utc_hour, \
|
||||
old_default_value) \
|
||||
UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, double)
|
||||
|
||||
#define UPDATE_from_string(name, year, month, day, utc_hour, \
|
||||
old_default_value) \
|
||||
UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, std::string)
|
||||
|
||||
#define UPDATE_from_path(name, year, month, day, utc_hour, old_default_value) \
|
||||
UPDATE_CVar(name, year, month, day, utc_hour, old_default_value, \
|
||||
std::filesystem::path)
|
||||
|
||||
} // namespace cvar
|
||||
|
||||
#endif // XENIA_CVAR_H_
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "xenia/base/fuzzy.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "xenia/base/xxhash.h"
|
||||
|
||||
namespace xe {
|
||||
namespace hash {
|
||||
|
||||
@@ -24,6 +26,13 @@ struct IdentityHasher {
|
||||
size_t operator()(const Key& key) const { return static_cast<size_t>(key); }
|
||||
};
|
||||
|
||||
template <typename Key>
|
||||
struct XXHasher {
|
||||
size_t operator()(const Key& key) const {
|
||||
return static_cast<size_t>(XXH3_64bits(&key, sizeof(key)));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace hash
|
||||
} // namespace xe
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef XENIA_BASE_MAIN_H_
|
||||
#define XENIA_BASE_MAIN_H_
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -25,19 +26,26 @@ bool has_console_attached();
|
||||
// launch.
|
||||
struct EntryInfo {
|
||||
std::string name;
|
||||
std::string positional_usage;
|
||||
std::vector<std::string> positional_options;
|
||||
int (*entry_point)(const std::vector<std::string>& args);
|
||||
bool transparent_options; // no argument parsing
|
||||
std::optional<std::string> positional_usage;
|
||||
std::optional<std::vector<std::string>> positional_options;
|
||||
};
|
||||
EntryInfo GetEntryInfo();
|
||||
|
||||
#define DEFINE_ENTRY_POINT(name, entry_point, positional_usage, ...) \
|
||||
xe::EntryInfo xe::GetEntryInfo() { \
|
||||
std::initializer_list<std::string> positional_options = {__VA_ARGS__}; \
|
||||
return xe::EntryInfo( \
|
||||
{name, positional_usage, \
|
||||
std::vector<std::string>(std::move(positional_options)), \
|
||||
entry_point}); \
|
||||
return xe::EntryInfo{ \
|
||||
name, entry_point, false, positional_usage, \
|
||||
std::vector<std::string>(std::move(positional_options))}; \
|
||||
}
|
||||
|
||||
// TODO(Joel Linn): Add some way to filter consumed arguments in
|
||||
// cvar::ParseLaunchArguments()
|
||||
#define DEFINE_ENTRY_POINT_TRANSPARENT(name, entry_point) \
|
||||
xe::EntryInfo xe::GetEntryInfo() { \
|
||||
return xe::EntryInfo{name, entry_point, true, std::nullopt, std::nullopt}; \
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -23,8 +23,10 @@ bool has_console_attached() { return true; }
|
||||
extern "C" int main(int argc, char** argv) {
|
||||
auto entry_info = xe::GetEntryInfo();
|
||||
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
|
||||
entry_info.positional_options);
|
||||
if (!entry_info.transparent_options) {
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
|
||||
entry_info.positional_options.value());
|
||||
}
|
||||
|
||||
std::vector<std::string> args;
|
||||
for (int n = 0; n < argc; n++) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -104,8 +104,10 @@ static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
|
||||
|
||||
LocalFree(wargv);
|
||||
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
|
||||
entry_info.positional_options);
|
||||
if (!entry_info.transparent_options) {
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
|
||||
entry_info.positional_options.value());
|
||||
}
|
||||
|
||||
args.clear();
|
||||
for (int n = 0; n < argc; n++) {
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | \
|
||||
WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES)
|
||||
#define XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
#endif
|
||||
|
||||
namespace xe {
|
||||
|
||||
class Win32MappedMemory : public MappedMemory {
|
||||
@@ -70,7 +75,7 @@ class Win32MappedMemory : public MappedMemory {
|
||||
size_t aligned_length = length + (offset - aligned_offset);
|
||||
|
||||
UnmapViewOfFile(data_);
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
data_ = MapViewOfFile(mapping_handle, view_access_, aligned_offset >> 32,
|
||||
aligned_offset & 0xFFFFFFFF, aligned_length);
|
||||
#else
|
||||
@@ -139,7 +144,7 @@ std::unique_ptr<MappedMemory> MappedMemory::Open(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
mm->mapping_handle = CreateFileMapping(
|
||||
mm->file_handle, nullptr, mapping_protect, DWORD(aligned_length >> 32),
|
||||
DWORD(aligned_length), nullptr);
|
||||
@@ -152,7 +157,7 @@ std::unique_ptr<MappedMemory> MappedMemory::Open(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
mm->data_ = reinterpret_cast<uint8_t*>(MapViewOfFile(
|
||||
mm->mapping_handle, view_access, DWORD(aligned_offset >> 32),
|
||||
DWORD(aligned_offset), aligned_length));
|
||||
@@ -257,7 +262,7 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
mapping_handle_ =
|
||||
CreateFileMapping(file_handle_, nullptr, mapping_protect,
|
||||
DWORD(capacity_ >> 32), DWORD(capacity_), nullptr);
|
||||
@@ -275,11 +280,11 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||
if (low_address_space) {
|
||||
bool successful = false;
|
||||
data_ = reinterpret_cast<uint8_t*>(0x10000000);
|
||||
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifndef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
HANDLE process = GetCurrentProcess();
|
||||
#endif
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
if (MapViewOfFileEx(mapping_handle_, view_access, 0, 0, capacity_,
|
||||
data_)) {
|
||||
successful = true;
|
||||
@@ -311,7 +316,7 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MAPPED_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
data_ = reinterpret_cast<uint8_t*>(
|
||||
MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_));
|
||||
#else
|
||||
|
||||
@@ -17,6 +17,16 @@
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <type_traits>
|
||||
|
||||
#if defined __has_include
|
||||
#if __has_include(<version>)
|
||||
#include <version>
|
||||
#endif
|
||||
#endif
|
||||
#if __cpp_lib_bitops
|
||||
#include <bit>
|
||||
#endif
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#if XE_ARCH_AMD64
|
||||
@@ -50,8 +60,20 @@ constexpr T round_up(T value, V multiple, bool force_non_zero = true) {
|
||||
return (value + multiple - 1) / multiple * multiple;
|
||||
}
|
||||
|
||||
constexpr float saturate(float value) {
|
||||
return std::max(std::min(1.0f, value), -1.0f);
|
||||
// Using the same conventions as in shading languages, returning 0 for NaN.
|
||||
// std::max is `a < b ? b : a`, thus in case of NaN, the first argument is
|
||||
// always returned. Also -0 is not < +0, so +0 is also chosen for it.
|
||||
template <typename T>
|
||||
constexpr T saturate_unsigned(T value) {
|
||||
return std::min(static_cast<T>(1.0f), std::max(static_cast<T>(0.0f), value));
|
||||
}
|
||||
|
||||
// This diverges from the GPU NaN rules for signed normalized formats (NaN
|
||||
// should be converted to 0, not to -1), but this expectation is not needed most
|
||||
// of time, and cannot be met for free (unlike for 0...1 clamping).
|
||||
template <typename T>
|
||||
constexpr T saturate_signed(T value) {
|
||||
return std::min(static_cast<T>(1.0f), std::max(static_cast<T>(-1.0f), value));
|
||||
}
|
||||
|
||||
// Gets the next power of two value that is greater than or equal to the given
|
||||
@@ -104,6 +126,23 @@ constexpr uint32_t select_bits(uint32_t value, uint32_t a, uint32_t b) {
|
||||
return (value & make_bitmask(a, b)) >> a;
|
||||
}
|
||||
|
||||
#if __cpp_lib_bitops
|
||||
template <class T>
|
||||
constexpr inline uint32_t bit_count(T v) {
|
||||
return static_cast<uint32_t>(std::popcount(v));
|
||||
}
|
||||
#else
|
||||
#if XE_COMPILER_MSVC || XE_COMPILER_INTEL
|
||||
inline uint32_t bit_count(uint32_t v) { return __popcnt(v); }
|
||||
inline uint32_t bit_count(uint64_t v) {
|
||||
return static_cast<uint32_t>(__popcnt64(v));
|
||||
}
|
||||
#elif XE_COMPILER_GCC || XE_COMPILER_CLANG
|
||||
static_assert(sizeof(unsigned int) == sizeof(uint32_t));
|
||||
static_assert(sizeof(unsigned long long) == sizeof(uint64_t));
|
||||
inline uint32_t bit_count(uint32_t v) { return __builtin_popcount(v); }
|
||||
inline uint32_t bit_count(uint64_t v) { return __builtin_popcountll(v); }
|
||||
#else
|
||||
inline uint32_t bit_count(uint32_t v) {
|
||||
v = v - ((v >> 1) & 0x55555555);
|
||||
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
|
||||
@@ -119,6 +158,8 @@ inline uint32_t bit_count(uint64_t v) {
|
||||
v = v + (v >> 32) & 0x0000007F;
|
||||
return static_cast<uint32_t>(v);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// lzcnt instruction, typed for integers of all sizes.
|
||||
// The number of leading zero bits in the value parameter. If value is zero, the
|
||||
@@ -245,7 +286,7 @@ inline bool bit_scan_forward(uint32_t v, uint32_t* out_first_set_index) {
|
||||
return i != 0;
|
||||
}
|
||||
inline bool bit_scan_forward(uint64_t v, uint32_t* out_first_set_index) {
|
||||
int i = ffsll(v);
|
||||
int i = __builtin_ffsll(v);
|
||||
*out_first_set_index = i - 1;
|
||||
return i != 0;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,16 @@ void copy_128_aligned(void* dest, const void* src, size_t count) {
|
||||
}
|
||||
|
||||
#if XE_ARCH_AMD64
|
||||
|
||||
// This works around a GCC bug
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100801
|
||||
// TODO(Joel Linn): Remove this when fixed GCC versions are common place.
|
||||
#if XE_COMPILER_GNUC
|
||||
#define XE_WORKAROUND_LOOP_KILL_MOD(x) \
|
||||
if ((count % (x)) == 0) __builtin_unreachable();
|
||||
#else
|
||||
#define XE_WORKAROUND_LOOP_KILL_MOD(x)
|
||||
#endif
|
||||
void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
assert_zero(reinterpret_cast<uintptr_t>(dest_ptr) & 0xF);
|
||||
@@ -61,6 +71,7 @@ void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(8);
|
||||
dest[i] = byte_swap(src[i]);
|
||||
}
|
||||
}
|
||||
@@ -80,6 +91,7 @@ void copy_and_swap_16_unaligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(8);
|
||||
dest[i] = byte_swap(src[i]);
|
||||
}
|
||||
}
|
||||
@@ -102,6 +114,7 @@ void copy_and_swap_32_aligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
||||
dest[i] = byte_swap(src[i]);
|
||||
}
|
||||
}
|
||||
@@ -121,6 +134,7 @@ void copy_and_swap_32_unaligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
||||
dest[i] = byte_swap(src[i]);
|
||||
}
|
||||
}
|
||||
@@ -143,6 +157,7 @@ void copy_and_swap_64_aligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(2);
|
||||
dest[i] = byte_swap(src[i]);
|
||||
}
|
||||
}
|
||||
@@ -162,6 +177,7 @@ void copy_and_swap_64_unaligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(2);
|
||||
dest[i] = byte_swap(src[i]);
|
||||
}
|
||||
}
|
||||
@@ -178,6 +194,7 @@ void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
||||
dest[i] = (src[i] >> 16) | (src[i] << 16);
|
||||
}
|
||||
}
|
||||
@@ -194,6 +211,7 @@ void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||
}
|
||||
for (; i < count; ++i) { // handle residual elements
|
||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
||||
dest[i] = (src[i] >> 16) | (src[i] << 16);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/byte_order.h"
|
||||
@@ -441,6 +442,26 @@ inline void store_and_swap<std::u16string>(void* mem,
|
||||
return store_and_swap<std::u16string_view>(mem, value);
|
||||
}
|
||||
|
||||
using fourcc_t = uint32_t;
|
||||
|
||||
// Get FourCC in host byte order
|
||||
// make_fourcc('a', 'b', 'c', 'd') == 0x61626364
|
||||
constexpr inline fourcc_t make_fourcc(char a, char b, char c, char d) {
|
||||
return fourcc_t((static_cast<fourcc_t>(a) << 24) |
|
||||
(static_cast<fourcc_t>(b) << 16) |
|
||||
(static_cast<fourcc_t>(c) << 8) | static_cast<fourcc_t>(d));
|
||||
}
|
||||
|
||||
// Get FourCC in host byte order
|
||||
// This overload requires fourcc.length() == 4
|
||||
// make_fourcc("abcd") == 'abcd' == 0x61626364 for most compilers
|
||||
constexpr inline fourcc_t make_fourcc(const std::string_view fourcc) {
|
||||
if (fourcc.length() != 4) {
|
||||
throw std::runtime_error("Invalid fourcc length");
|
||||
}
|
||||
return make_fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_MEMORY_H_
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
|
||||
#include "xenia/base/platform_win.h"
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | \
|
||||
WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES)
|
||||
#define XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
#endif
|
||||
|
||||
namespace xe {
|
||||
namespace memory {
|
||||
|
||||
@@ -75,12 +80,11 @@ PageAccess ToXeniaProtectFlags(DWORD access) {
|
||||
}
|
||||
|
||||
bool IsWritableExecutableMemorySupported() {
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
return true;
|
||||
#else
|
||||
// To test FromApp functions on desktop, replace
|
||||
// WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) with 0 in the #ifs and
|
||||
// link to WindowsApp.lib.
|
||||
// To test FromApp functions on desktop, undefine
|
||||
// XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS and link to WindowsApp.lib.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -103,7 +107,7 @@ void* AllocFixed(void* base_address, size_t length,
|
||||
break;
|
||||
}
|
||||
DWORD protect = ToWin32ProtectFlags(access);
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
return VirtualAlloc(base_address, length, alloc_type, protect);
|
||||
#else
|
||||
return VirtualAllocFromApp(base_address, length, ULONG(alloc_type),
|
||||
@@ -135,7 +139,7 @@ bool Protect(void* base_address, size_t length, PageAccess access,
|
||||
*out_old_access = PageAccess::kNoAccess;
|
||||
}
|
||||
DWORD new_protect = ToWin32ProtectFlags(access);
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
DWORD old_protect = 0;
|
||||
BOOL result = VirtualProtect(base_address, length, new_protect, &old_protect);
|
||||
#else
|
||||
@@ -174,7 +178,7 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
|
||||
DWORD protect =
|
||||
ToWin32ProtectFlags(access) | (commit ? SEC_COMMIT : SEC_RESERVE);
|
||||
auto full_path = "Local" / path;
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
return CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, protect,
|
||||
static_cast<DWORD>(length >> 32),
|
||||
static_cast<DWORD>(length), full_path.c_str());
|
||||
@@ -191,7 +195,7 @@ void CloseFileMappingHandle(FileMappingHandle handle,
|
||||
|
||||
void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
|
||||
PageAccess access, size_t file_offset) {
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
#ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS
|
||||
DWORD target_address_low = static_cast<DWORD>(file_offset);
|
||||
DWORD target_address_high = static_cast<DWORD>(file_offset >> 32);
|
||||
DWORD file_access = 0;
|
||||
|
||||
@@ -85,18 +85,17 @@
|
||||
#endif // XE_PLATFORM_MAC
|
||||
|
||||
#if XE_COMPILER_MSVC
|
||||
#define XEPACKEDSTRUCT(name, value) \
|
||||
__pragma(pack(push, 1)) struct name value __pragma(pack(pop));
|
||||
#define XEPACKEDSTRUCTANONYMOUS(value) \
|
||||
__pragma(pack(push, 1)) struct value __pragma(pack(pop));
|
||||
#define XEPACKEDUNION(name, value) \
|
||||
__pragma(pack(push, 1)) union name value __pragma(pack(pop));
|
||||
#define _XEPACKEDSCOPE(body) __pragma(pack(push, 1)) body __pragma(pack(pop));
|
||||
#else
|
||||
#define XEPACKEDSTRUCT(name, value) struct __attribute__((packed)) name value;
|
||||
#define XEPACKEDSTRUCTANONYMOUS(value) struct __attribute__((packed)) value;
|
||||
#define XEPACKEDUNION(name, value) union __attribute__((packed)) name value;
|
||||
#define _XEPACKEDSCOPE(body) \
|
||||
_Pragma("pack(push, 1)") body; \
|
||||
_Pragma("pack(pop)");
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
#define XEPACKEDSTRUCT(name, value) _XEPACKEDSCOPE(struct name value)
|
||||
#define XEPACKEDSTRUCTANONYMOUS(value) _XEPACKEDSCOPE(struct value)
|
||||
#define XEPACKEDUNION(name, value) _XEPACKEDSCOPE(union name value)
|
||||
|
||||
namespace xe {
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#define NOMINMAX
|
||||
#include <ObjBase.h>
|
||||
#include <SDKDDKVer.h>
|
||||
#include <bcrypt.h>
|
||||
#include <dwmapi.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
@@ -87,12 +87,12 @@ struct string_key_case : internal::string_key_base {
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct std::hash<xe::string_key> {
|
||||
struct hash<xe::string_key> {
|
||||
std::size_t operator()(const xe::string_key& t) const { return t.hash(); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<xe::string_key_case> {
|
||||
struct hash<xe::string_key_case> {
|
||||
std::size_t operator()(const xe::string_key_case& t) const {
|
||||
return t.hash();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -18,7 +18,7 @@ namespace xe {
|
||||
namespace base {
|
||||
namespace test {
|
||||
|
||||
TEST_CASE("copy_128_aligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_128_aligned", "[copy_and_swap]") {
|
||||
alignas(128) uint8_t src[256], dest[256];
|
||||
for (uint8_t i = 0; i < 255; ++i) {
|
||||
src[i] = 255 - i;
|
||||
@@ -37,7 +37,7 @@ TEST_CASE("copy_128_aligned", "Copy and Swap") {
|
||||
REQUIRE(std::memcmp(dest, src + 1, 128));
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_16_aligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_16_aligned", "[copy_and_swap]") {
|
||||
alignas(16) uint16_t a = 0x1111, b = 0xABCD;
|
||||
copy_and_swap_16_aligned(&a, &b, 1);
|
||||
REQUIRE(a == 0xCDAB);
|
||||
@@ -93,7 +93,7 @@ TEST_CASE("copy_and_swap_16_aligned", "Copy and Swap") {
|
||||
REQUIRE(std::strcmp(f, "s atdnra dlagimnne.t") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_16_unaligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_16_unaligned", "[copy_and_swap]") {
|
||||
uint16_t a = 0x1111, b = 0xABCD;
|
||||
copy_and_swap_16_unaligned(&a, &b, 1);
|
||||
REQUIRE(a == 0xCDAB);
|
||||
@@ -139,7 +139,7 @@ TEST_CASE("copy_and_swap_16_unaligned", "Copy and Swap") {
|
||||
"noeg rhtnas atdnra dlagimnne.t") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_32_aligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_32_aligned", "[copy_and_swap]") {
|
||||
alignas(32) uint32_t a = 0x11111111, b = 0x89ABCDEF;
|
||||
copy_and_swap_32_aligned(&a, &b, 1);
|
||||
REQUIRE(a == 0xEFCDAB89);
|
||||
@@ -195,7 +195,7 @@ TEST_CASE("copy_and_swap_32_aligned", "Copy and Swap") {
|
||||
REQUIRE(std::strcmp(f, "ats radnla dmngi.tne") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_32_unaligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_32_unaligned", "[copy_and_swap]") {
|
||||
uint32_t a = 0x11111111, b = 0x89ABCDEF;
|
||||
copy_and_swap_32_unaligned(&a, &b, 1);
|
||||
REQUIRE(a == 0xEFCDAB89);
|
||||
@@ -259,7 +259,7 @@ TEST_CASE("copy_and_swap_32_unaligned", "Copy and Swap") {
|
||||
"regnahtats radnla dmngi.tne") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_64_aligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_64_aligned", "[copy_and_swap]") {
|
||||
alignas(64) uint64_t a = 0x1111111111111111, b = 0x0123456789ABCDEF;
|
||||
copy_and_swap_64_aligned(&a, &b, 1);
|
||||
REQUIRE(a == 0xEFCDAB8967452301);
|
||||
@@ -317,7 +317,7 @@ TEST_CASE("copy_and_swap_64_aligned", "Copy and Swap") {
|
||||
REQUIRE(std::strcmp(f, "radnats mngila d") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_64_unaligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_64_unaligned", "[copy_and_swap]") {
|
||||
uint64_t a = 0x1111111111111111, b = 0x0123456789ABCDEF;
|
||||
copy_and_swap_64_unaligned(&a, &b, 1);
|
||||
REQUIRE(a == 0xEFCDAB8967452301);
|
||||
@@ -407,12 +407,12 @@ TEST_CASE("copy_and_swap_64_unaligned", "Copy and Swap") {
|
||||
"regradnats mngila d") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_16_in_32_aligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_16_in_32_aligned", "[copy_and_swap]") {
|
||||
// TODO(bwrsandman): test once properly understood.
|
||||
REQUIRE(true == true);
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_16_in_32_unaligned", "Copy and Swap") {
|
||||
TEST_CASE("copy_and_swap_16_in_32_unaligned", "[copy_and_swap]") {
|
||||
// TODO(bwrsandman): test once properly understood.
|
||||
REQUIRE(true == true);
|
||||
}
|
||||
@@ -425,7 +425,7 @@ TEST_CASE("create_and_close_file_mapping", "Virtual Memory Mapping") {
|
||||
xe::memory::CloseFileMappingHandle(memory, path);
|
||||
}
|
||||
|
||||
TEST_CASE("map_view", "Virtual Memory Mapping") {
|
||||
TEST_CASE("map_view", "[virtual_memory_mapping]") {
|
||||
auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount());
|
||||
const size_t length = 0x100;
|
||||
auto memory = xe::memory::CreateFileMappingHandle(
|
||||
@@ -442,7 +442,7 @@ TEST_CASE("map_view", "Virtual Memory Mapping") {
|
||||
xe::memory::CloseFileMappingHandle(memory, path);
|
||||
}
|
||||
|
||||
TEST_CASE("read_write_view", "Virtual Memory Mapping") {
|
||||
TEST_CASE("read_write_view", "[virtual_memory_mapping]") {
|
||||
const size_t length = 0x100;
|
||||
auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount());
|
||||
auto memory = xe::memory::CreateFileMappingHandle(
|
||||
@@ -469,6 +469,40 @@ TEST_CASE("read_write_view", "Virtual Memory Mapping") {
|
||||
xe::memory::CloseFileMappingHandle(memory, path);
|
||||
}
|
||||
|
||||
TEST_CASE("make_fourcc", "[fourcc]") {
|
||||
SECTION("'1234'") {
|
||||
const uint32_t fourcc_host = 0x31323334;
|
||||
constexpr fourcc_t fourcc_1 = make_fourcc('1', '2', '3', '4');
|
||||
constexpr fourcc_t fourcc_2 = make_fourcc("1234");
|
||||
REQUIRE(fourcc_1 == fourcc_host);
|
||||
REQUIRE(fourcc_2 == fourcc_host);
|
||||
REQUIRE(fourcc_1 == fourcc_2);
|
||||
REQUIRE(fourcc_2 == fourcc_1);
|
||||
}
|
||||
|
||||
SECTION("'ABcd'") {
|
||||
const uint32_t fourcc_host = 0x41426364;
|
||||
constexpr fourcc_t fourcc_1 = make_fourcc('A', 'B', 'c', 'd');
|
||||
constexpr fourcc_t fourcc_2 = make_fourcc("ABcd");
|
||||
REQUIRE(fourcc_1 == fourcc_host);
|
||||
REQUIRE(fourcc_2 == fourcc_host);
|
||||
REQUIRE(fourcc_1 == fourcc_2);
|
||||
REQUIRE(fourcc_2 == fourcc_1);
|
||||
}
|
||||
|
||||
SECTION("'XEN\\0'") {
|
||||
const uint32_t fourcc_host = 0x58454E00;
|
||||
constexpr fourcc_t fourcc = make_fourcc('X', 'E', 'N', '\0');
|
||||
REQUIRE(fourcc == fourcc_host);
|
||||
}
|
||||
|
||||
SECTION("length()!=4") {
|
||||
REQUIRE_THROWS(make_fourcc("AB\0\0"));
|
||||
REQUIRE_THROWS(make_fourcc("AB\0\0AB"));
|
||||
REQUIRE_THROWS(make_fourcc("ABCDEFGH"));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace base
|
||||
} // namespace xe
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2018 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -84,17 +84,17 @@ TEST_CASE("Enable process to set thread affinity") {
|
||||
EnableAffinityConfiguration();
|
||||
}
|
||||
|
||||
TEST_CASE("Yield Current Thread", "MaybeYield") {
|
||||
TEST_CASE("Yield Current Thread", "[maybe_yield]") {
|
||||
// Run to see if there are any errors
|
||||
MaybeYield();
|
||||
}
|
||||
|
||||
TEST_CASE("Sync with Memory Barrier", "SyncMemory") {
|
||||
TEST_CASE("Sync with Memory Barrier", "[sync_memory]") {
|
||||
// Run to see if there are any errors
|
||||
SyncMemory();
|
||||
}
|
||||
|
||||
TEST_CASE("Sleep Current Thread", "Sleep") {
|
||||
TEST_CASE("Sleep Current Thread", "[sleep]") {
|
||||
auto wait_time = 50ms;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
Sleep(wait_time);
|
||||
@@ -102,7 +102,7 @@ TEST_CASE("Sleep Current Thread", "Sleep") {
|
||||
REQUIRE(duration >= wait_time);
|
||||
}
|
||||
|
||||
TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") {
|
||||
TEST_CASE("Sleep Current Thread in Alertable State", "[sleep]") {
|
||||
auto wait_time = 50ms;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
auto result = threading::AlertableSleep(wait_time);
|
||||
@@ -154,7 +154,7 @@ TEST_CASE("HighResolutionTimer") {
|
||||
// Time the actual sleep duration
|
||||
{
|
||||
const auto interval = 50ms;
|
||||
std::atomic<uint64_t> counter;
|
||||
std::atomic<uint64_t> counter(0);
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
auto cb = [&counter] { ++counter; };
|
||||
auto pTimer = HighResolutionTimer::CreateRepeating(interval, cb);
|
||||
@@ -201,7 +201,7 @@ TEST_CASE("HighResolutionTimer") {
|
||||
// spawned from differing threads
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Multiple Handles", "Wait") {
|
||||
TEST_CASE("Wait on Multiple Handles", "[wait]") {
|
||||
auto mutant = Mutant::Create(true);
|
||||
auto semaphore = Semaphore::Create(10, 10);
|
||||
auto event_ = Event::CreateManualResetEvent(false);
|
||||
@@ -244,7 +244,7 @@ TEST_CASE("Signal and Wait") {
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Event", "Event") {
|
||||
TEST_CASE("Wait on Event", "[event]") {
|
||||
auto evt = Event::CreateAutoResetEvent(false);
|
||||
WaitResult result;
|
||||
|
||||
@@ -262,7 +262,7 @@ TEST_CASE("Wait on Event", "Event") {
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
}
|
||||
|
||||
TEST_CASE("Reset Event", "Event") {
|
||||
TEST_CASE("Reset Event", "[event]") {
|
||||
auto evt = Event::CreateAutoResetEvent(false);
|
||||
WaitResult result;
|
||||
|
||||
@@ -283,7 +283,7 @@ TEST_CASE("Reset Event", "Event") {
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Multiple Events", "Event") {
|
||||
TEST_CASE("Wait on Multiple Events", "[event]") {
|
||||
auto events = std::array<std::unique_ptr<Event>, 4>{
|
||||
Event::CreateAutoResetEvent(false),
|
||||
Event::CreateAutoResetEvent(false),
|
||||
@@ -348,7 +348,7 @@ TEST_CASE("Wait on Multiple Events", "Event") {
|
||||
// REQUIRE(order[3] == '3');
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Semaphore", "Semaphore") {
|
||||
TEST_CASE("Wait on Semaphore", "[semaphore]") {
|
||||
WaitResult result;
|
||||
std::unique_ptr<Semaphore> sem;
|
||||
int previous_count = 0;
|
||||
@@ -406,9 +406,13 @@ TEST_CASE("Wait on Semaphore", "Semaphore") {
|
||||
sem = Semaphore::Create(5, 5);
|
||||
Sleep(10ms);
|
||||
// Occupy the semaphore with 5 threads
|
||||
auto func = [&sem] {
|
||||
std::atomic<int> wait_count(0);
|
||||
volatile bool threads_terminate(false);
|
||||
auto func = [&sem, &wait_count, &threads_terminate] {
|
||||
auto res = Wait(sem.get(), false, 100ms);
|
||||
Sleep(500ms);
|
||||
wait_count++;
|
||||
while (!threads_terminate) {
|
||||
}
|
||||
if (res == WaitResult::kSuccess) {
|
||||
sem->Release(1, nullptr);
|
||||
}
|
||||
@@ -417,12 +421,14 @@ TEST_CASE("Wait on Semaphore", "Semaphore") {
|
||||
std::thread(func), std::thread(func), std::thread(func),
|
||||
std::thread(func), std::thread(func),
|
||||
};
|
||||
// Give threads time to acquire semaphore
|
||||
Sleep(10ms);
|
||||
// Wait for threads to finish semaphore calls
|
||||
while (wait_count != 5) {
|
||||
}
|
||||
// Attempt to acquire full semaphore with current (6th) thread
|
||||
result = Wait(sem.get(), false, 20ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
// Give threads time to release semaphore
|
||||
threads_terminate = true;
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
@@ -444,7 +450,7 @@ TEST_CASE("Wait on Semaphore", "Semaphore") {
|
||||
// REQUIRE(sem.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Multiple Semaphores", "Semaphore") {
|
||||
TEST_CASE("Wait on Multiple Semaphores", "[semaphore]") {
|
||||
WaitResult all_result;
|
||||
std::pair<WaitResult, size_t> any_result;
|
||||
int previous_count;
|
||||
@@ -501,7 +507,7 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") {
|
||||
REQUIRE(previous_count == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Mutant", "Mutant") {
|
||||
TEST_CASE("Wait on Mutant", "[mutant]") {
|
||||
WaitResult result;
|
||||
std::unique_ptr<Mutant> mut;
|
||||
|
||||
@@ -558,7 +564,7 @@ TEST_CASE("Wait on Mutant", "Mutant") {
|
||||
REQUIRE(mut->Release());
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Multiple Mutants", "Mutant") {
|
||||
TEST_CASE("Wait on Multiple Mutants", "[mutant]") {
|
||||
WaitResult all_result;
|
||||
std::pair<WaitResult, size_t> any_result;
|
||||
std::unique_ptr<Mutant> mut0, mut1;
|
||||
@@ -621,7 +627,7 @@ TEST_CASE("Wait on Multiple Mutants", "Mutant") {
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Timer", "Timer") {
|
||||
TEST_CASE("Wait on Timer", "[timer]") {
|
||||
WaitResult result;
|
||||
std::unique_ptr<Timer> timer;
|
||||
|
||||
@@ -686,7 +692,7 @@ TEST_CASE("Wait on Timer", "Timer") {
|
||||
REQUIRE(result == WaitResult::kTimeout); // No more signals from repeating
|
||||
}
|
||||
|
||||
TEST_CASE("Wait on Multiple Timers", "Timer") {
|
||||
TEST_CASE("Wait on Multiple Timers", "[timer]") {
|
||||
WaitResult all_result;
|
||||
std::pair<WaitResult, size_t> any_result;
|
||||
|
||||
@@ -724,13 +730,13 @@ TEST_CASE("Wait on Multiple Timers", "Timer") {
|
||||
REQUIRE(any_result.second == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Create and Trigger Timer Callbacks", "Timer") {
|
||||
TEST_CASE("Create and Trigger Timer Callbacks", "[timer]") {
|
||||
// TODO(bwrsandman): Check which thread performs callback and timing of
|
||||
// callback
|
||||
REQUIRE(true);
|
||||
}
|
||||
|
||||
TEST_CASE("Set and Test Current Thread ID", "Thread") {
|
||||
TEST_CASE("Set and Test Current Thread ID", "[thread]") {
|
||||
// System ID
|
||||
auto system_id = current_thread_system_id();
|
||||
REQUIRE(system_id > 0);
|
||||
@@ -763,71 +769,76 @@ TEST_CASE("Set and Test Current Thread Name", "Thread") {
|
||||
REQUIRE_NOTHROW(set_name(old_thread_name));
|
||||
}
|
||||
|
||||
TEST_CASE("Create and Run Thread", "Thread") {
|
||||
TEST_CASE("Create and Run Thread", "[thread]") {
|
||||
std::unique_ptr<Thread> thread;
|
||||
WaitResult result;
|
||||
Thread::CreationParameters params = {};
|
||||
auto func = [] { Sleep(20ms); };
|
||||
|
||||
// Create most basic case of thread
|
||||
thread = Thread::Create(params, func);
|
||||
REQUIRE(thread->native_handle() != nullptr);
|
||||
REQUIRE_NOTHROW(thread->affinity_mask());
|
||||
REQUIRE(thread->name().empty());
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
SECTION("Create most basic case of thread") {
|
||||
thread = Thread::Create(params, func);
|
||||
REQUIRE(thread->native_handle() != nullptr);
|
||||
REQUIRE_NOTHROW(thread->affinity_mask());
|
||||
REQUIRE(thread->name().empty());
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
// Add thread name
|
||||
std::string new_name = "Test thread name";
|
||||
thread = Thread::Create(params, func);
|
||||
auto name = thread->name();
|
||||
INFO(name.c_str());
|
||||
REQUIRE(name.empty());
|
||||
thread->set_name(new_name);
|
||||
REQUIRE(thread->name() == new_name);
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
SECTION("Add thread name") {
|
||||
std::string new_name = "Test thread name";
|
||||
thread = Thread::Create(params, func);
|
||||
auto name = thread->name();
|
||||
INFO(name.c_str());
|
||||
REQUIRE(name.empty());
|
||||
thread->set_name(new_name);
|
||||
REQUIRE(thread->name() == new_name);
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
// Use Terminate to end an infinitely looping thread
|
||||
thread = Thread::Create(params, [] {
|
||||
while (true) {
|
||||
Sleep(1ms);
|
||||
}
|
||||
});
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
thread->Terminate(-1);
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
SECTION("Use Terminate to end an infinitely looping thread") {
|
||||
thread = Thread::Create(params, [] {
|
||||
while (true) {
|
||||
Sleep(1ms);
|
||||
}
|
||||
});
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
thread->Terminate(-1);
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
// Call Exit from inside an infinitely looping thread
|
||||
thread = Thread::Create(params, [] {
|
||||
while (true) {
|
||||
SECTION("Call Exit from inside an infinitely looping thread") {
|
||||
thread = Thread::Create(params, [] {
|
||||
Thread::Exit(-1);
|
||||
}
|
||||
});
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
FAIL("Function must not return");
|
||||
});
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
// Call timeout wait on self
|
||||
result = Wait(Thread::GetCurrentThread(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
SECTION("Call timeout wait on self") {
|
||||
result = Wait(Thread::GetCurrentThread(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
}
|
||||
|
||||
params.stack_size = 16 * 1024 * 1024;
|
||||
thread = Thread::Create(params, [] {
|
||||
while (true) {
|
||||
SECTION("16kb stack size") {
|
||||
params.stack_size = 16 * 1024 * 1024;
|
||||
thread = Thread::Create(params, [] {
|
||||
Thread::Exit(-1);
|
||||
}
|
||||
});
|
||||
REQUIRE(thread != nullptr);
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
FAIL("Function must not return");
|
||||
});
|
||||
REQUIRE(thread != nullptr);
|
||||
result = Wait(thread.get(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
// TODO(bwrsandman): Test with different priorities
|
||||
// TODO(bwrsandman): Test setting and getting thread affinity
|
||||
}
|
||||
|
||||
TEST_CASE("Test Suspending Thread", "Thread") {
|
||||
TEST_CASE("Test Suspending Thread", "[thread]") {
|
||||
std::unique_ptr<Thread> thread;
|
||||
WaitResult result;
|
||||
Thread::CreationParameters params = {};
|
||||
@@ -888,7 +899,7 @@ TEST_CASE("Test Suspending Thread", "Thread") {
|
||||
REQUIRE(result == threading::WaitResult::kSuccess);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Thread QueueUserCallback", "Thread") {
|
||||
TEST_CASE("Test Thread QueueUserCallback", "[thread]") {
|
||||
std::unique_ptr<Thread> thread;
|
||||
WaitResult result;
|
||||
Thread::CreationParameters params = {};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -16,16 +16,220 @@
|
||||
|
||||
namespace xe::base::test {
|
||||
|
||||
// TODO(gibbed): bit messy?
|
||||
// TODO(gibbed): predicate variant?
|
||||
|
||||
#define TEST_EXAMPLE(func, left, right) REQUIRE(func(left) == right)
|
||||
|
||||
#define TEST_EXAMPLES_1(func, language, results) \
|
||||
TEST_EXAMPLE(func, examples::k##language##Values[0], results.language[0])
|
||||
#define TEST_EXAMPLES_2(func, language, results) \
|
||||
TEST_EXAMPLE(func, examples::k##language##Values[0], results.language[0]); \
|
||||
TEST_EXAMPLE(func, examples::k##language##Values[1], results.language[1])
|
||||
#define TEST_EXAMPLES_3(func, language, results) \
|
||||
TEST_EXAMPLE(func, examples::k##language##Values[0], results.language[0]); \
|
||||
TEST_EXAMPLE(func, examples::k##language##Values[1], results.language[1]); \
|
||||
TEST_EXAMPLE(func, examples::k##language##Values[2], results.language[2])
|
||||
|
||||
namespace examples {
|
||||
|
||||
// https://www.cl.cam.ac.uk/~mgk25/ucs/examples/quickbrown.txt
|
||||
|
||||
TEST_CASE("utf8::split", "UTF-8 Split") {
|
||||
const size_t kDanishCount = 1;
|
||||
const char* kDanishValues[kDanishCount] = {
|
||||
u8"Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther "
|
||||
u8"spillede på xylofon.",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Danish(func, results) \
|
||||
TEST_EXAMPLES_1(func, Danish, results)
|
||||
|
||||
const size_t kGermanCount = 3;
|
||||
const char* kGermanValues[kGermanCount] = {
|
||||
u8"Falsches Üben von Xylophonmusik quält jeden größeren Zwerg",
|
||||
u8"Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich",
|
||||
u8"Heizölrückstoßabdämpfung",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_German(func, results) \
|
||||
TEST_EXAMPLES_2(func, German, results)
|
||||
|
||||
const size_t kGreekCount = 2;
|
||||
const char* kGreekValues[kGreekCount] = {
|
||||
u8"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο",
|
||||
u8"Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Greek(func, results) \
|
||||
TEST_EXAMPLES_2(func, Greek, results)
|
||||
|
||||
const size_t kEnglishCount = 1;
|
||||
const char* kEnglishValues[kEnglishCount] = {
|
||||
u8"The quick brown fox jumps over the lazy dog",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_English(func, results) \
|
||||
TEST_EXAMPLES_1(func, English, results)
|
||||
|
||||
const size_t kSpanishCount = 1;
|
||||
const char* kSpanishValues[kSpanishCount] = {
|
||||
u8"El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, "
|
||||
u8"añoraba a su querido cachorro.",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Spanish(func, results) \
|
||||
TEST_EXAMPLES_1(func, Spanish, results)
|
||||
|
||||
const size_t kFrenchCount = 3;
|
||||
const char* kFrenchValues[kFrenchCount] = {
|
||||
u8"Portez ce vieux whisky au juge blond qui fume sur son île intérieure, à "
|
||||
u8"côté de l'alcôve ovoïde, où les bûches se consument dans l'âtre, ce qui "
|
||||
u8"lui permet de penser à la cænogenèse de l'être dont il est question "
|
||||
u8"dans la cause ambiguë entendue à Moÿ, dans un capharnaüm qui, "
|
||||
u8"pense-t-il, diminue çà et là la qualité de son œuvre.",
|
||||
u8"l'île exiguë\n"
|
||||
u8"Où l'obèse jury mûr\n"
|
||||
u8"Fête l'haï volapük,\n"
|
||||
u8"Âne ex aéquo au whist,\n"
|
||||
u8"Ôtez ce vœu déçu.",
|
||||
u8"Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en canoë "
|
||||
u8"au delà des îles, près du mälström où brûlent les novæ.",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_French(func, results) \
|
||||
TEST_EXAMPLES_3(func, French, results)
|
||||
|
||||
const size_t kIrishGaelicCount = 1;
|
||||
const char* kIrishGaelicValues[kIrishGaelicCount] = {
|
||||
u8"D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_IrishGaelic(func, results) \
|
||||
TEST_EXAMPLES_1(func, IrishGaelic, results)
|
||||
|
||||
const size_t kHungarianCount = 1;
|
||||
const char* kHungarianValues[kHungarianCount] = {
|
||||
u8"Árvíztűrő tükörfúrógép",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Hungarian(func, results) \
|
||||
TEST_EXAMPLES_1(func, Hungarian, results)
|
||||
|
||||
const size_t kIcelandicCount = 2;
|
||||
const char* kIcelandicValues[kIcelandicCount] = {
|
||||
u8"Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa",
|
||||
u8"Sævör grét áðan því úlpan var ónýt",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Icelandic(func, results) \
|
||||
TEST_EXAMPLES_2(func, Icelandic, results)
|
||||
|
||||
const size_t kJapaneseCount = 2;
|
||||
const char* kJapaneseValues[kJapaneseCount] = {
|
||||
u8"いろはにほへとちりぬるを\n"
|
||||
u8"わかよたれそつねならむ\n"
|
||||
u8"うゐのおくやまけふこえて\n"
|
||||
u8"あさきゆめみしゑひもせす\n",
|
||||
u8"イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム\n"
|
||||
u8"ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Japanese(func, results) \
|
||||
TEST_EXAMPLES_2(func, Japanese, results)
|
||||
|
||||
const size_t kHebrewCount = 1;
|
||||
const char* kHebrewValues[kHebrewCount] = {
|
||||
u8"? דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Hebrew(func, results) \
|
||||
TEST_EXAMPLES_1(func, Hebrew, results)
|
||||
|
||||
const size_t kPolishCount = 1;
|
||||
const char* kPolishValues[kPolishCount] = {
|
||||
u8"Pchnąć w tę łódź jeża lub ośm skrzyń fig",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Polish(func, results) \
|
||||
TEST_EXAMPLES_1(func, Polish, results)
|
||||
|
||||
const size_t kRussianCount = 2;
|
||||
const char* kRussianValues[kRussianCount] = {
|
||||
u8"В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!",
|
||||
u8"Съешь же ещё этих мягких французских булок да выпей чаю",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Russian(func, results) \
|
||||
TEST_EXAMPLES_2(func, Russian, results)
|
||||
|
||||
const size_t kTurkishCount = 1;
|
||||
const char* kTurkishValues[kTurkishCount] = {
|
||||
u8"Pijamalı hasta, yağız şoföre çabucak güvendi.",
|
||||
};
|
||||
#define TEST_LANGUAGE_EXAMPLES_Turkish(func, results) \
|
||||
TEST_EXAMPLES_1(func, Turkish, results)
|
||||
|
||||
#define TEST_LANGUAGE_EXAMPLES(func, results) \
|
||||
TEST_LANGUAGE_EXAMPLES_Danish(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_German(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Greek(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_English(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Spanish(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_French(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_IrishGaelic(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Hungarian(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Icelandic(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Japanese(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Hebrew(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Polish(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Russian(func, results); \
|
||||
TEST_LANGUAGE_EXAMPLES_Turkish(func, results)
|
||||
|
||||
} // namespace examples
|
||||
|
||||
#define TEST_EXAMPLE_RESULT(language) T language[examples::k##language##Count]
|
||||
template <typename T>
|
||||
struct example_results {
|
||||
TEST_EXAMPLE_RESULT(Danish);
|
||||
TEST_EXAMPLE_RESULT(German);
|
||||
TEST_EXAMPLE_RESULT(Greek);
|
||||
TEST_EXAMPLE_RESULT(English);
|
||||
TEST_EXAMPLE_RESULT(Spanish);
|
||||
TEST_EXAMPLE_RESULT(French);
|
||||
TEST_EXAMPLE_RESULT(IrishGaelic);
|
||||
TEST_EXAMPLE_RESULT(Hungarian);
|
||||
TEST_EXAMPLE_RESULT(Icelandic);
|
||||
TEST_EXAMPLE_RESULT(Japanese);
|
||||
TEST_EXAMPLE_RESULT(Hebrew);
|
||||
TEST_EXAMPLE_RESULT(Polish);
|
||||
TEST_EXAMPLE_RESULT(Russian);
|
||||
TEST_EXAMPLE_RESULT(Turkish);
|
||||
};
|
||||
#undef TEST_EXAMPLE_RESULT
|
||||
|
||||
TEST_CASE("UTF-8 Count", "[utf8]") {
|
||||
example_results<size_t> results = {};
|
||||
results.Danish[0] = 88;
|
||||
results.German[0] = 58;
|
||||
results.German[1] = 54;
|
||||
results.Greek[0] = 52;
|
||||
results.Greek[1] = 33;
|
||||
results.English[0] = 43;
|
||||
results.Spanish[0] = 99;
|
||||
results.French[0] = 327;
|
||||
results.French[1] = 93;
|
||||
results.French[2] = 126;
|
||||
results.IrishGaelic[0] = 68;
|
||||
results.Hungarian[0] = 22;
|
||||
results.Icelandic[0] = 50;
|
||||
results.Icelandic[1] = 34;
|
||||
results.Japanese[0] = 51;
|
||||
results.Japanese[1] = 55;
|
||||
results.Hebrew[0] = 52;
|
||||
results.Polish[0] = 40;
|
||||
results.Russian[0] = 54;
|
||||
results.Russian[1] = 55;
|
||||
results.Turkish[0] = 45;
|
||||
TEST_LANGUAGE_EXAMPLES(utf8::count, results);
|
||||
}
|
||||
|
||||
// TODO(gibbed): lower_ascii
|
||||
// TODO(gibbed): upper_ascii
|
||||
// TODO(gibbed): hash_fnv1a
|
||||
// TODO(gibbed): hash_fnv1a_case
|
||||
|
||||
TEST_CASE("UTF-8 Split", "[utf8]") {
|
||||
std::vector<std::string_view> parts;
|
||||
|
||||
// Danish
|
||||
parts = utf8::split(
|
||||
u8"Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther "
|
||||
u8"spillede på xylofon.",
|
||||
u8"æcå");
|
||||
parts = utf8::split(examples::kDanishValues[0], u8"æcå");
|
||||
REQUIRE(parts.size() == 4);
|
||||
REQUIRE(parts[0] == u8"Quizdeltagerne spiste jordb");
|
||||
REQUIRE(parts[1] == u8"r med fløde, mens ");
|
||||
@@ -33,43 +237,41 @@ TEST_CASE("utf8::split", "UTF-8 Split") {
|
||||
REQUIRE(parts[3] == u8" xylofon.");
|
||||
|
||||
// German
|
||||
parts = utf8::split(
|
||||
u8"Falsches Üben von Xylophonmusik quält jeden größeren Zwerg\n"
|
||||
u8"Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich\n"
|
||||
u8"Heizölrückstoßabdämpfung",
|
||||
u8"ßS");
|
||||
REQUIRE(parts.size() == 4);
|
||||
parts = utf8::split(examples::kGermanValues[0], u8"ßS");
|
||||
REQUIRE(parts.size() == 2);
|
||||
REQUIRE(parts[0] == u8"Falsches Üben von Xylophonmusik quält jeden grö");
|
||||
REQUIRE(parts[1] ==
|
||||
u8"eren Zwerg\nZwölf Boxkämpfer jagten Eva quer über den ");
|
||||
REQUIRE(parts[2] == u8"ylter Deich\nHeizölrücksto");
|
||||
REQUIRE(parts[3] == u8"abdämpfung");
|
||||
REQUIRE(parts[1] == u8"eren Zwerg");
|
||||
parts = utf8::split(examples::kGermanValues[1], u8"ßS");
|
||||
REQUIRE(parts.size() == 2);
|
||||
REQUIRE(parts[0] == u8"Zwölf Boxkämpfer jagten Eva quer über den ");
|
||||
REQUIRE(parts[1] == u8"ylter Deich");
|
||||
parts = utf8::split(examples::kGermanValues[2], u8"ßS");
|
||||
REQUIRE(parts.size() == 2);
|
||||
REQUIRE(parts[0] == u8"Heizölrücksto");
|
||||
REQUIRE(parts[1] == u8"abdämpfung");
|
||||
|
||||
// Greek
|
||||
parts = utf8::split(
|
||||
u8"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο\n"
|
||||
u8"Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία",
|
||||
u8"πφ");
|
||||
REQUIRE(parts.size() == 6);
|
||||
parts = utf8::split(examples::kGreekValues[0], u8"πφ");
|
||||
REQUIRE(parts.size() == 4);
|
||||
REQUIRE(parts[0] == u8"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ ");
|
||||
REQUIRE(parts[1] == u8"ιὰ στὸ χρυσα");
|
||||
REQUIRE(parts[2] == u8"ὶ ξέ");
|
||||
REQUIRE(parts[3] == u8"ωτο\nΞεσκε");
|
||||
REQUIRE(parts[4] == u8"άζω τὴν ψυχο");
|
||||
REQUIRE(parts[5] == u8"θόρα βδελυγμία");
|
||||
REQUIRE(parts[3] == u8"ωτο");
|
||||
parts = utf8::split(examples::kGreekValues[1], u8"πφ");
|
||||
REQUIRE(parts.size() == 3);
|
||||
REQUIRE(parts[0] == u8"Ξεσκε");
|
||||
REQUIRE(parts[1] == u8"άζω τὴν ψυχο");
|
||||
REQUIRE(parts[2] == u8"θόρα βδελυγμία");
|
||||
|
||||
// English
|
||||
parts = utf8::split("The quick brown fox jumps over the lazy dog", "xy");
|
||||
parts = utf8::split(examples::kEnglishValues[0], "xy");
|
||||
REQUIRE(parts.size() == 3);
|
||||
REQUIRE(parts[0] == u8"The quick brown fo");
|
||||
REQUIRE(parts[1] == u8" jumps over the laz");
|
||||
REQUIRE(parts[2] == u8" dog");
|
||||
|
||||
// Spanish
|
||||
parts = utf8::split(
|
||||
u8"El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y "
|
||||
u8"frío, añoraba a su querido cachorro.",
|
||||
u8"ójd");
|
||||
parts = utf8::split(examples::kSpanishValues[0], u8"ójd");
|
||||
REQUIRE(parts.size() == 4);
|
||||
REQUIRE(parts[0] == u8"El pingüino Wenceslao hizo kil");
|
||||
REQUIRE(parts[1] == u8"metros ba");
|
||||
@@ -88,52 +290,254 @@ TEST_CASE("utf8::split", "UTF-8 Split") {
|
||||
// TODO(gibbed): Turkish
|
||||
}
|
||||
|
||||
TEST_CASE("utf8::equal_z", "UTF-8 Equal Z") {
|
||||
TEST_CASE("UTF-8 Equal Z", "[utf8]") {
|
||||
REQUIRE(utf8::equal_z(u8"foo", u8"foo\0"));
|
||||
REQUIRE_FALSE(utf8::equal_z(u8"bar", u8"baz\0"));
|
||||
}
|
||||
|
||||
TEST_CASE("utf8::equal_case_z", "UTF-8 Equal Case Z") {
|
||||
REQUIRE(utf8::equal_z(u8"foo", u8"foo\0"));
|
||||
REQUIRE_FALSE(utf8::equal_z(u8"bar", u8"baz\0"));
|
||||
TEST_CASE("UTF-8 Equal Case", "[utf8]") {
|
||||
REQUIRE(utf8::equal_case(u8"foo", u8"foo\0"));
|
||||
REQUIRE_FALSE(utf8::equal_case(u8"bar", u8"baz\0"));
|
||||
}
|
||||
|
||||
TEST_CASE("utf8::join_paths", "UTF-8 Join Paths") {
|
||||
REQUIRE(utf8::join_paths({u8"X:", u8"foo", u8"bar", u8"baz", u8"qux"},
|
||||
'\\') == "X:\\foo\\bar\\baz\\qux");
|
||||
REQUIRE(utf8::join_paths({u8"X:", u8"foo", u8"bar", u8"baz", u8"qux"}, '/') ==
|
||||
"X:/foo/bar/baz/qux");
|
||||
TEST_CASE("UTF-8 Equal Case Z", "[utf8]") {
|
||||
REQUIRE(utf8::equal_case_z(u8"foo", u8"foo\0"));
|
||||
REQUIRE_FALSE(utf8::equal_case_z(u8"bar", u8"baz\0"));
|
||||
}
|
||||
|
||||
TEST_CASE("utf8::fix_path_separators", "UTF-8 Fix Path Separators") {
|
||||
REQUIRE(utf8::fix_path_separators("X:\\foo/bar\\baz/qux", '\\') ==
|
||||
"X:\\foo\\bar\\baz\\qux");
|
||||
REQUIRE(utf8::fix_path_separators("X:\\foo/bar\\baz/qux", '/') ==
|
||||
"X:/foo/bar/baz/qux");
|
||||
// TODO(gibbed): find_any_of
|
||||
// TODO(gibbed): find_any_of_case
|
||||
// TODO(gibbed): find_first_of
|
||||
// TODO(gibbed): find_first_of_case
|
||||
// TODO(gibbed): starts_with
|
||||
// TODO(gibbed): starts_with_case
|
||||
// TODO(gibbed): ends_with
|
||||
// TODO(gibbed): ends_with_case
|
||||
// TODO(gibbed): split_path
|
||||
|
||||
#define TEST_PATH(func, input, output) \
|
||||
do { \
|
||||
std::string input_value = input; \
|
||||
std::string output_value = output; \
|
||||
REQUIRE(func(input_value, '/') == output_value); \
|
||||
std::replace(input_value.begin(), input_value.end(), '/', '\\'); \
|
||||
std::replace(output_value.begin(), output_value.end(), '/', '\\'); \
|
||||
REQUIRE(func(input_value, '\\') == output_value); \
|
||||
} while (0)
|
||||
|
||||
#define TEST_PATH_RAW(func, input, output) \
|
||||
do { \
|
||||
std::string output_value = output; \
|
||||
REQUIRE(func(input, '/') == output_value); \
|
||||
std::replace(output_value.begin(), output_value.end(), '/', '\\'); \
|
||||
REQUIRE(func(input, '\\') == output_value); \
|
||||
} while (0)
|
||||
|
||||
#define TEST_PATHS(func, output, ...) \
|
||||
do { \
|
||||
std::vector<std::string> input_values = {__VA_ARGS__}; \
|
||||
std::string output_value = output; \
|
||||
REQUIRE(func(input_values, '/') == output_value); \
|
||||
for (auto it = input_values.begin(); it != input_values.end(); ++it) { \
|
||||
std::replace((*it).begin(), (*it).end(), '/', '\\'); \
|
||||
} \
|
||||
std::replace(output_value.begin(), output_value.end(), '/', '\\'); \
|
||||
REQUIRE(func(input_values, '\\') == output_value); \
|
||||
} while (0)
|
||||
|
||||
TEST_CASE("UTF-8 Join Paths", "[utf8]") {
|
||||
TEST_PATHS(utf8::join_paths, u8"");
|
||||
TEST_PATHS(utf8::join_paths, u8"foo", u8"foo");
|
||||
TEST_PATHS(utf8::join_paths, u8"foo/bar", u8"foo", u8"bar");
|
||||
TEST_PATHS(utf8::join_paths, "X:/foo/bar/baz/qux", u8"X:", u8"foo", u8"bar",
|
||||
u8"baz", u8"qux");
|
||||
}
|
||||
|
||||
TEST_CASE("utf8::find_name_from_path", "UTF-8 Find Name From Path") {
|
||||
REQUIRE(utf8::find_name_from_path("X:\\foo\\bar\\baz\\qux", '\\') == "qux");
|
||||
REQUIRE(utf8::find_name_from_path("X:/foo/bar/baz/qux", '/') == "qux");
|
||||
// TODO(gibbed): join_guest_paths
|
||||
|
||||
TEST_CASE("UTF-8 Fix Path Separators", "[utf8]") {
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "", "");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "\\", "/");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "/", "/");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "\\foo", "/foo");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "\\foo/", "/foo/");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "/foo", "/foo");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "\\foo/bar\\baz/qux",
|
||||
"/foo/bar/baz/qux");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "\\\\foo//bar\\\\baz//qux",
|
||||
"/foo/bar/baz/qux");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "foo", "foo");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "foo/", "foo/");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "foo/bar\\baz/qux",
|
||||
"foo/bar/baz/qux");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "foo//bar\\\\baz//qux",
|
||||
"foo/bar/baz/qux");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:", "X:");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:\\", "X:/");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:/", "X:/");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:\\foo", "X:/foo");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:\\foo/", "X:/foo/");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:/foo", "X:/foo");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:\\foo/bar\\baz/qux",
|
||||
"X:/foo/bar/baz/qux");
|
||||
TEST_PATH_RAW(utf8::fix_path_separators, "X:\\\\foo//bar\\\\baz//qux",
|
||||
"X:/foo/bar/baz/qux");
|
||||
}
|
||||
|
||||
TEST_CASE("utf8::find_base_path", "UTF-8 Find Base Path") {
|
||||
REQUIRE(utf8::find_base_path("X:\\foo\\bar\\baz\\qux", '\\') ==
|
||||
"X:\\foo\\bar\\baz");
|
||||
REQUIRE(utf8::find_base_path("X:/foo/bar/baz/qux", '/') == "X:/foo/bar/baz");
|
||||
// TODO(gibbed): fix_guest_path_separators
|
||||
|
||||
TEST_CASE("UTF-8 Find Name From Path", "[utf8]") {
|
||||
TEST_PATH(utf8::find_name_from_path, "/", "");
|
||||
TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux/", "qux");
|
||||
TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux.txt", "qux.txt");
|
||||
TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"ほげほげ");
|
||||
TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
|
||||
"ほげほげ.txt");
|
||||
TEST_PATH(utf8::find_name_from_path, "/foo/bar/baz/qux.txt", "qux.txt");
|
||||
TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"ほげほげ");
|
||||
TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
|
||||
"ほげほげ.txt");
|
||||
TEST_PATH(utf8::find_name_from_path, "X:/foo/bar/baz/qux.txt", "qux.txt");
|
||||
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"ほげほげ");
|
||||
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
|
||||
"ほげほげ.txt");
|
||||
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ",
|
||||
"ほげら.ほげほげ");
|
||||
}
|
||||
|
||||
TEST_CASE("utf8::canonicalize_path", "UTF-8 Canonicalize Path") {
|
||||
REQUIRE(utf8::canonicalize_path("X:\\foo\\bar\\baz\\qux", '\\') ==
|
||||
"X:\\foo\\bar\\baz\\qux");
|
||||
REQUIRE(utf8::canonicalize_path("X:\\foo\\.\\baz\\qux", '\\') ==
|
||||
"X:\\foo\\baz\\qux");
|
||||
REQUIRE(utf8::canonicalize_path("X:\\foo\\..\\baz\\qux", '\\') ==
|
||||
"X:\\baz\\qux");
|
||||
REQUIRE(utf8::canonicalize_path("X:\\.\\bar\\baz\\qux", '\\') ==
|
||||
"X:\\bar\\baz\\qux");
|
||||
REQUIRE(utf8::canonicalize_path("X:\\..\\bar\\baz\\qux", '\\') ==
|
||||
"X:\\bar\\baz\\qux");
|
||||
// TODO(gibbed): find_name_from_guest_path
|
||||
|
||||
TEST_CASE("UTF-8 Find Base Name From Path", "[utf8]") {
|
||||
TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux.txt", "qux");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux/", "qux");
|
||||
TEST_PATH(utf8::find_base_name_from_path,
|
||||
"ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"ほげほげ");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら.ほげほげ",
|
||||
"ほげら");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux.txt", "qux");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux/", "qux");
|
||||
TEST_PATH(utf8::find_base_name_from_path,
|
||||
"/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"ほげほげ");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら.ほげほげ",
|
||||
"ほげら");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux.txt", "qux");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux/", "qux");
|
||||
TEST_PATH(utf8::find_base_name_from_path,
|
||||
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ");
|
||||
TEST_PATH(utf8::find_base_name_from_path,
|
||||
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ");
|
||||
TEST_PATH(utf8::find_base_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ",
|
||||
"ほげら");
|
||||
}
|
||||
|
||||
// TODO(gibbed): find_base_name_from_guest_path
|
||||
|
||||
TEST_CASE("UTF-8 Find Base Path", "[utf8]") {
|
||||
TEST_PATH(utf8::find_base_path, "", "");
|
||||
TEST_PATH(utf8::find_base_path, "/", "");
|
||||
TEST_PATH(utf8::find_base_path, "//", "");
|
||||
TEST_PATH(utf8::find_base_path, "/foo", "");
|
||||
TEST_PATH(utf8::find_base_path, "/foo/", "");
|
||||
TEST_PATH(utf8::find_base_path, "/foo/bar", "/foo");
|
||||
TEST_PATH(utf8::find_base_path, "/foo/bar/", "/foo");
|
||||
TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux", "/foo/bar/baz");
|
||||
TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux/", "/foo/bar/baz");
|
||||
TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ",
|
||||
"/ほげ/ぴよ/ふが/ほげら");
|
||||
TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"/ほげ/ぴよ/ふが/ほげら");
|
||||
TEST_PATH(utf8::find_base_path, "foo", "");
|
||||
TEST_PATH(utf8::find_base_path, "foo/", "");
|
||||
TEST_PATH(utf8::find_base_path, "foo/bar", "foo");
|
||||
TEST_PATH(utf8::find_base_path, "foo/bar/", "foo");
|
||||
TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux", "foo/bar/baz");
|
||||
TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux/", "foo/bar/baz");
|
||||
TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ",
|
||||
"ほげ/ぴよ/ふが/ほげら");
|
||||
TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"ほげ/ぴよ/ふが/ほげら");
|
||||
TEST_PATH(utf8::find_base_path, "X:", "");
|
||||
TEST_PATH(utf8::find_base_path, "X:/", "");
|
||||
TEST_PATH(utf8::find_base_path, "X:/foo", "X:");
|
||||
TEST_PATH(utf8::find_base_path, "X:/foo/", "X:");
|
||||
TEST_PATH(utf8::find_base_path, "X:/foo/bar", "X:/foo");
|
||||
TEST_PATH(utf8::find_base_path, "X:/foo/bar/", "X:/foo");
|
||||
TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux", "X:/foo/bar/baz");
|
||||
TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux/", "X:/foo/bar/baz");
|
||||
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ",
|
||||
"X:/ほげ/ぴよ/ふが/ほげら");
|
||||
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
|
||||
"X:/ほげ/ぴよ/ふが/ほげら");
|
||||
}
|
||||
|
||||
// TODO(gibbed): find_base_guest_path
|
||||
|
||||
TEST_CASE("UTF-8 Canonicalize Path", "[utf8]") {
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux", "foo/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux/", "foo/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux", "foo/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux/", "foo/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/../baz/qux", "baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/../baz/qux/", "baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/./baz/../qux", "foo/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/./baz/../qux/", "foo/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/./../baz/qux", "baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "foo/./../baz/qux/", "baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "./bar/baz/qux", "bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "./bar/baz/qux/", "bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "../bar/baz/qux", "bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "../bar/baz/qux/", "bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "ほげ/ぴよ/./ふが/../ほげら/ほげほげ",
|
||||
"ほげ/ぴよ/ほげら/ほげほげ");
|
||||
TEST_PATH(utf8::canonicalize_path, "ほげ/ぴよ/./ふが/../ほげら/ほげほげ/",
|
||||
"ほげ/ぴよ/ほげら/ほげほげ");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/bar/baz/qux", "/foo/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/bar/baz/qux/", "/foo/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/./baz/qux", "/foo/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/./baz/qux/", "/foo/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/../baz/qux", "/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/../baz/qux/", "/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/./baz/../qux", "/foo/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/./baz/../qux/", "/foo/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/./../baz/qux", "/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/foo/./../baz/qux/", "/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/./bar/baz/qux", "/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/./bar/baz/qux/", "/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/../bar/baz/qux", "/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/../bar/baz/qux/", "/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "/ほげ/ぴよ/./ふが/../ほげら/ほげほげ",
|
||||
"/ほげ/ぴよ/ほげら/ほげほげ");
|
||||
TEST_PATH(utf8::canonicalize_path, "/ほげ/ぴよ/./ふが/../ほげら/ほげほげ/",
|
||||
"/ほげ/ぴよ/ほげら/ほげほげ");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/bar/baz/qux",
|
||||
"X:/foo/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/bar/baz/qux/",
|
||||
"X:/foo/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/./baz/qux", "X:/foo/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/./baz/qux/", "X:/foo/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/../baz/qux", "X:/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/../baz/qux/", "X:/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/./baz/../qux", "X:/foo/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/./baz/../qux/", "X:/foo/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/./../baz/qux", "X:/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/foo/./../baz/qux/", "X:/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/./bar/baz/qux", "X:/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/./bar/baz/qux/", "X:/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/../bar/baz/qux", "X:/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/../bar/baz/qux/", "X:/bar/baz/qux");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/ほげ/ぴよ/./ふが/../ほげら/ほげほげ",
|
||||
"X:/ほげ/ぴよ/ほげら/ほげほげ");
|
||||
TEST_PATH(utf8::canonicalize_path, "X:/ほげ/ぴよ/./ふが/../ほげら/ほげほげ/",
|
||||
"X:/ほげ/ぴよ/ほげら/ほげほげ");
|
||||
}
|
||||
|
||||
// TODO(gibbed): canonicalize_guest_path
|
||||
|
||||
} // namespace xe::base::test
|
||||
|
||||
@@ -155,29 +155,36 @@ bool SetTlsValue(TlsHandle handle, uintptr_t value) {
|
||||
class PosixHighResolutionTimer : public HighResolutionTimer {
|
||||
public:
|
||||
explicit PosixHighResolutionTimer(std::function<void()> callback)
|
||||
: callback_(std::move(callback)), timer_(nullptr) {}
|
||||
: callback_(std::move(callback)), valid_(false) {}
|
||||
~PosixHighResolutionTimer() override {
|
||||
if (timer_) timer_delete(timer_);
|
||||
if (valid_) timer_delete(timer_);
|
||||
}
|
||||
|
||||
bool Initialize(std::chrono::milliseconds period) {
|
||||
if (valid_) {
|
||||
// Double initialization
|
||||
assert_always();
|
||||
return false;
|
||||
}
|
||||
// Create timer
|
||||
sigevent sev{};
|
||||
sev.sigev_notify = SIGEV_SIGNAL;
|
||||
sev.sigev_signo = GetSystemSignal(SignalType::kHighResolutionTimer);
|
||||
sev.sigev_value.sival_ptr = (void*)&callback_;
|
||||
if (timer_create(CLOCK_REALTIME, &sev, &timer_) == -1) return false;
|
||||
if (timer_create(CLOCK_MONOTONIC, &sev, &timer_) == -1) return false;
|
||||
|
||||
// Start timer
|
||||
itimerspec its{};
|
||||
its.it_value = DurationToTimeSpec(period);
|
||||
its.it_interval = its.it_value;
|
||||
return timer_settime(timer_, 0, &its, nullptr) != -1;
|
||||
valid_ = timer_settime(timer_, 0, &its, nullptr) != -1;
|
||||
return valid_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> callback_;
|
||||
timer_t timer_;
|
||||
bool valid_; // all values for timer_t are legal so we need this
|
||||
};
|
||||
|
||||
std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
|
||||
@@ -187,7 +194,7 @@ std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
|
||||
if (!timer->Initialize(period)) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<HighResolutionTimer>(timer.release());
|
||||
return std::move(timer);
|
||||
}
|
||||
|
||||
class PosixConditionBase {
|
||||
@@ -419,7 +426,7 @@ class PosixCondition<Timer> : public PosixConditionBase {
|
||||
sev.sigev_notify = SIGEV_SIGNAL;
|
||||
sev.sigev_signo = GetSystemSignal(SignalType::kTimer);
|
||||
sev.sigev_value.sival_ptr = this;
|
||||
if (timer_create(CLOCK_REALTIME, &sev, &timer_) == -1) return false;
|
||||
if (timer_create(CLOCK_MONOTONIC, &sev, &timer_) == -1) return false;
|
||||
}
|
||||
|
||||
// Start timer
|
||||
@@ -728,31 +735,44 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
||||
}
|
||||
|
||||
void Terminate(int exit_code) {
|
||||
bool is_current_thread = pthread_self() == thread_;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(state_mutex_);
|
||||
if (state_ == State::kFinished) {
|
||||
if (is_current_thread) {
|
||||
// This is really bad. Some thread must have called Terminate() on us
|
||||
// just before we decided to terminate ourselves
|
||||
assert_always();
|
||||
for (;;) {
|
||||
// Wait for pthread_cancel() to actually happen.
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
state_ = State::kFinished;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// Sometimes the thread can call terminate twice before stopping
|
||||
if (thread_ == 0) return;
|
||||
auto thread = thread_;
|
||||
|
||||
exit_code_ = exit_code;
|
||||
signaled_ = true;
|
||||
cond_.notify_all();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
exit_code_ = exit_code;
|
||||
signaled_ = true;
|
||||
cond_.notify_all();
|
||||
}
|
||||
if (is_current_thread) {
|
||||
pthread_exit(reinterpret_cast<void*>(exit_code));
|
||||
} else {
|
||||
#ifdef XE_PLATFORM_ANDROID
|
||||
if (pthread_kill(thread, GetSystemSignal(SignalType::kThreadTerminate)) !=
|
||||
0) {
|
||||
assert_always();
|
||||
}
|
||||
if (pthread_kill(thread_,
|
||||
GetSystemSignal(SignalType::kThreadTerminate)) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
#else
|
||||
if (pthread_cancel(thread) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
if (pthread_cancel(thread_) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void WaitStarted() const {
|
||||
@@ -778,7 +798,6 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
||||
inline void post_execution() override {
|
||||
if (thread_) {
|
||||
pthread_join(thread_, nullptr);
|
||||
thread_ = 0;
|
||||
}
|
||||
}
|
||||
pthread_t thread_;
|
||||
@@ -1115,13 +1134,12 @@ Thread* Thread::GetCurrentThread() {
|
||||
void Thread::Exit(int exit_code) {
|
||||
if (current_thread_) {
|
||||
current_thread_->Terminate(exit_code);
|
||||
// Sometimes the current thread keeps running after being cancelled.
|
||||
// Prevent other calls from this thread from using current_thread_.
|
||||
current_thread_ = nullptr;
|
||||
} else {
|
||||
// Should only happen with the main thread
|
||||
pthread_exit(reinterpret_cast<void*>(exit_code));
|
||||
}
|
||||
// Function must not return
|
||||
assert_always();
|
||||
}
|
||||
|
||||
void set_name(const std::string_view name) {
|
||||
|
||||
@@ -111,30 +111,34 @@ bool SetTlsValue(TlsHandle handle, uintptr_t value) {
|
||||
class Win32HighResolutionTimer : public HighResolutionTimer {
|
||||
public:
|
||||
Win32HighResolutionTimer(std::function<void()> callback)
|
||||
: callback_(callback) {}
|
||||
: callback_(std::move(callback)) {}
|
||||
~Win32HighResolutionTimer() override {
|
||||
if (handle_) {
|
||||
if (valid_) {
|
||||
DeleteTimerQueueTimer(nullptr, handle_, INVALID_HANDLE_VALUE);
|
||||
handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Initialize(std::chrono::milliseconds period) {
|
||||
return CreateTimerQueueTimer(
|
||||
&handle_, nullptr,
|
||||
[](PVOID param, BOOLEAN timer_or_wait_fired) {
|
||||
auto timer =
|
||||
reinterpret_cast<Win32HighResolutionTimer*>(param);
|
||||
timer->callback_();
|
||||
},
|
||||
this, 0, DWORD(period.count()), WT_EXECUTEINTIMERTHREAD)
|
||||
? true
|
||||
: false;
|
||||
if (valid_) {
|
||||
// Double initialization
|
||||
assert_always();
|
||||
return false;
|
||||
}
|
||||
valid_ = !!CreateTimerQueueTimer(
|
||||
&handle_, nullptr,
|
||||
[](PVOID param, BOOLEAN timer_or_wait_fired) {
|
||||
auto timer = reinterpret_cast<Win32HighResolutionTimer*>(param);
|
||||
timer->callback_();
|
||||
},
|
||||
this, 0, DWORD(period.count()), WT_EXECUTEINTIMERTHREAD);
|
||||
return valid_;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE handle_ = nullptr;
|
||||
std::function<void()> callback_;
|
||||
HANDLE handle_ = nullptr;
|
||||
bool valid_ = false; // Documentation does not state which HANDLE is invalid
|
||||
};
|
||||
|
||||
std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
|
||||
@@ -143,7 +147,7 @@ std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
|
||||
if (!timer->Initialize(period)) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<HighResolutionTimer>(timer.release());
|
||||
return std::move(timer);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
namespace utfcpp = utf8;
|
||||
|
||||
using citer = std::string_view::const_iterator;
|
||||
using criter = std::string_view::const_reverse_iterator;
|
||||
using utf8_citer = utfcpp::iterator<std::string_view::const_iterator>;
|
||||
using utf8_criter = utfcpp::iterator<std::string_view::const_reverse_iterator>;
|
||||
|
||||
namespace xe::utf8 {
|
||||
|
||||
@@ -54,25 +52,10 @@ std::pair<utf8_citer, utf8_citer> make_citer(const utf8_citer begin,
|
||||
utf8_citer(end.base(), begin.base(), end.base())};
|
||||
}
|
||||
|
||||
std::pair<utf8_criter, utf8_criter> make_criter(const std::string_view view) {
|
||||
return {utf8_criter(view.crbegin(), view.crbegin(), view.crend()),
|
||||
utf8_criter(view.crend(), view.crbegin(), view.crend())};
|
||||
}
|
||||
|
||||
std::pair<utf8_criter, utf8_criter> make_criter(const utf8_criter begin,
|
||||
const utf8_criter end) {
|
||||
return {utf8_criter(begin.base(), begin.base(), end.base()),
|
||||
utf8_criter(end.base(), begin.base(), end.base())};
|
||||
}
|
||||
|
||||
size_t byte_length(utf8_citer begin, utf8_citer end) {
|
||||
return size_t(std::distance(begin.base(), end.base()));
|
||||
}
|
||||
|
||||
size_t byte_length(utf8_criter begin, utf8_criter end) {
|
||||
return size_t(std::distance(begin.base(), end.base()));
|
||||
}
|
||||
|
||||
size_t count(const std::string_view view) {
|
||||
return size_t(utfcpp::distance(view.cbegin(), view.cend()));
|
||||
}
|
||||
@@ -435,21 +418,23 @@ bool ends_with(const std::string_view haystack, const std::string_view needle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto [haystack_begin, haystack_end] = make_criter(haystack);
|
||||
auto [needle_begin, needle_end] = make_criter(needle);
|
||||
auto [haystack_begin, haystack_end] = make_citer(haystack);
|
||||
auto [needle_begin, needle_end] = make_citer(needle);
|
||||
auto needle_count = count(needle);
|
||||
|
||||
auto it = haystack_begin;
|
||||
auto it = haystack_end;
|
||||
auto end = it;
|
||||
for (size_t i = 0; i < needle_count; ++i) {
|
||||
if (end == haystack_end) {
|
||||
--it;
|
||||
|
||||
for (size_t i = 1; i < needle_count; ++i) {
|
||||
if (it == haystack_begin) {
|
||||
// not enough room in target for search
|
||||
return false;
|
||||
}
|
||||
++end;
|
||||
--it;
|
||||
}
|
||||
|
||||
auto [sub_start, sub_end] = make_criter(it, end);
|
||||
auto [sub_start, sub_end] = make_citer(it, end);
|
||||
return std::equal(needle_begin, needle_end, sub_start, sub_end);
|
||||
}
|
||||
|
||||
@@ -461,21 +446,23 @@ bool ends_with_case(const std::string_view haystack,
|
||||
return false;
|
||||
}
|
||||
|
||||
auto [haystack_begin, haystack_end] = make_criter(haystack);
|
||||
auto [needle_begin, needle_end] = make_criter(needle);
|
||||
auto [haystack_begin, haystack_end] = make_citer(haystack);
|
||||
auto [needle_begin, needle_end] = make_citer(needle);
|
||||
auto needle_count = count(needle);
|
||||
|
||||
auto it = haystack_begin;
|
||||
auto it = haystack_end;
|
||||
auto end = it;
|
||||
--it;
|
||||
|
||||
for (size_t i = 0; i < needle_count; ++i) {
|
||||
if (end == haystack_end) {
|
||||
if (it == haystack_begin) {
|
||||
// not enough room in target for search
|
||||
return false;
|
||||
}
|
||||
++end;
|
||||
--it;
|
||||
}
|
||||
|
||||
auto [sub_start, sub_end] = make_criter(it, end);
|
||||
auto [sub_start, sub_end] = make_citer(it, end);
|
||||
return std::equal(needle_begin, needle_end, sub_start, sub_end,
|
||||
equal_ascii_case);
|
||||
}
|
||||
@@ -492,7 +479,9 @@ std::string join_paths(const std::string_view left_path,
|
||||
return std::string(left_path);
|
||||
}
|
||||
|
||||
auto [it, end] = make_criter(left_path);
|
||||
utf8_citer it;
|
||||
std::tie(std::ignore, it) = make_citer(left_path);
|
||||
--it;
|
||||
|
||||
std::string result = std::string(left_path);
|
||||
if (*it != static_cast<uint32_t>(separator)) {
|
||||
@@ -501,7 +490,20 @@ std::string join_paths(const std::string_view left_path,
|
||||
return result + std::string(right_path);
|
||||
}
|
||||
|
||||
std::string join_paths(std::vector<std::string_view> paths,
|
||||
std::string join_paths(const std::vector<std::string>& paths,
|
||||
char32_t separator) {
|
||||
std::string result;
|
||||
auto it = paths.cbegin();
|
||||
if (it != paths.cend()) {
|
||||
result = *it++;
|
||||
for (; it != paths.cend(); ++it) {
|
||||
result = join_paths(result, *it, separator);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string join_paths(const std::vector<std::string_view>& paths,
|
||||
char32_t separator) {
|
||||
std::string result;
|
||||
auto it = paths.cbegin();
|
||||
@@ -528,8 +530,20 @@ std::string fix_path_separators(const std::string_view path,
|
||||
std::string result;
|
||||
auto it = path_begin;
|
||||
auto last = it;
|
||||
|
||||
auto is_separator = [old_separator, new_separator](char32_t c) {
|
||||
return c == uint32_t(old_separator) || c == uint32_t(new_separator);
|
||||
};
|
||||
|
||||
// Begins with a separator
|
||||
if (is_separator(*it)) {
|
||||
utfcpp::append(new_separator, result);
|
||||
++it;
|
||||
last = it;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
it = std::find(it, path_end, uint32_t(old_separator));
|
||||
it = std::find_if(it, path_end, is_separator);
|
||||
if (it == path_end) {
|
||||
break;
|
||||
}
|
||||
@@ -563,25 +577,40 @@ std::string find_name_from_path(const std::string_view path,
|
||||
return std::string();
|
||||
}
|
||||
|
||||
auto [begin, end] = make_criter(path);
|
||||
auto [begin, end] = make_citer(path);
|
||||
|
||||
auto it = begin;
|
||||
auto it = end;
|
||||
--it;
|
||||
|
||||
// path is padded with separator
|
||||
size_t padding = 0;
|
||||
if (*it == uint32_t(separator)) {
|
||||
++it;
|
||||
if (it == begin) {
|
||||
return std::string();
|
||||
}
|
||||
--it;
|
||||
padding = 1;
|
||||
}
|
||||
|
||||
if (it == end) {
|
||||
// path is just separator
|
||||
if (it == begin) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
it = std::find(it, end, uint32_t(separator));
|
||||
if (it == end) {
|
||||
// search for separator
|
||||
while (it != begin) {
|
||||
if (*it == uint32_t(separator)) {
|
||||
break;
|
||||
}
|
||||
--it;
|
||||
}
|
||||
|
||||
// no separator -- copy entire string (except trailing separator)
|
||||
if (it == begin) {
|
||||
return std::string(path.substr(0, path.size() - padding));
|
||||
}
|
||||
|
||||
auto length = byte_length(begin, it);
|
||||
auto length = byte_length(std::next(it), end);
|
||||
auto offset = path.length() - length;
|
||||
return std::string(path.substr(offset, length - padding));
|
||||
}
|
||||
@@ -593,20 +622,25 @@ std::string find_base_name_from_path(const std::string_view path,
|
||||
return std::string();
|
||||
}
|
||||
|
||||
auto [begin, end] = make_criter(name);
|
||||
auto [begin, end] = make_citer(name);
|
||||
|
||||
auto it = std::find(begin, end, uint32_t('.'));
|
||||
if (it == end) {
|
||||
auto it = end;
|
||||
--it;
|
||||
|
||||
while (it != begin) {
|
||||
if (*it == uint32_t('.')) {
|
||||
break;
|
||||
}
|
||||
--it;
|
||||
}
|
||||
|
||||
if (it == begin) {
|
||||
return name;
|
||||
}
|
||||
|
||||
it++;
|
||||
if (it == end) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
auto length = name.length() - byte_length(begin, it);
|
||||
return std::string(name.substr(0, length));
|
||||
auto length = byte_length(it, end);
|
||||
auto offset = name.length() - length;
|
||||
return std::string(name.substr(0, offset));
|
||||
}
|
||||
|
||||
std::string find_base_path(const std::string_view path, char32_t separator) {
|
||||
@@ -614,25 +648,33 @@ std::string find_base_path(const std::string_view path, char32_t separator) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
auto [begin, end] = make_criter(path);
|
||||
auto [begin, end] = make_citer(path);
|
||||
|
||||
auto it = begin;
|
||||
auto it = end;
|
||||
--it;
|
||||
|
||||
// skip trailing separator
|
||||
if (*it == uint32_t(separator)) {
|
||||
++it;
|
||||
if (it == begin) {
|
||||
return std::string();
|
||||
}
|
||||
--it;
|
||||
}
|
||||
|
||||
it = std::find(it, end, uint32_t(separator));
|
||||
if (it == end) {
|
||||
while (it != begin) {
|
||||
if (*it == uint32_t(separator)) {
|
||||
break;
|
||||
}
|
||||
--it;
|
||||
}
|
||||
|
||||
if (it == begin) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
++it;
|
||||
if (it == end) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
auto length = path.length() - byte_length(begin, it);
|
||||
return std::string(path.substr(0, length));
|
||||
auto length = byte_length(it, end);
|
||||
auto offset = path.length() - length;
|
||||
return std::string(path.substr(0, offset));
|
||||
}
|
||||
|
||||
std::string canonicalize_path(const std::string_view path, char32_t separator) {
|
||||
|
||||
@@ -68,7 +68,10 @@ std::string join_paths(const std::string_view left_path,
|
||||
const std::string_view right_path,
|
||||
char32_t separator = kPathSeparator);
|
||||
|
||||
std::string join_paths(std::vector<std::string_view> paths,
|
||||
std::string join_paths(const std::vector<std::string>& paths,
|
||||
char32_t separator = kPathSeparator);
|
||||
|
||||
std::string join_paths(const std::vector<std::string_view>& paths,
|
||||
char32_t separator = kPathSeparator);
|
||||
|
||||
inline std::string join_paths(
|
||||
@@ -86,7 +89,12 @@ inline std::string join_guest_paths(const std::string_view left_path,
|
||||
return join_paths(left_path, right_path, kGuestPathSeparator);
|
||||
}
|
||||
|
||||
inline std::string join_guest_paths(std::vector<std::string_view> paths) {
|
||||
inline std::string join_guest_paths(const std::vector<std::string>& paths) {
|
||||
return join_paths(paths, kGuestPathSeparator);
|
||||
}
|
||||
|
||||
inline std::string join_guest_paths(
|
||||
const std::vector<std::string_view>& paths) {
|
||||
return join_paths(paths, kGuestPathSeparator);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,18 +106,6 @@ typedef struct alignas(16) vec128_s {
|
||||
};
|
||||
};
|
||||
|
||||
vec128_s() = default;
|
||||
vec128_s(const vec128_s& other) {
|
||||
high = other.high;
|
||||
low = other.low;
|
||||
}
|
||||
|
||||
vec128_s& operator=(const vec128_s& b) {
|
||||
high = b.high;
|
||||
low = b.low;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const vec128_s& b) const {
|
||||
return low == b.low && high == b.high;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user