Merge branch 'master' into vulkan

This commit is contained in:
Triang3l
2021-06-08 12:15:34 +03:00
607 changed files with 452347 additions and 127070 deletions

View File

@@ -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() {

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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_

View File

@@ -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;

View File

@@ -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_

View File

@@ -9,6 +9,7 @@
#include "xenia/base/fuzzy.h"
#include <cctype>
#include <cstring>
#include <iostream>

View File

@@ -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

View File

@@ -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

View File

@@ -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++) {

View File

@@ -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++) {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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_

View File

@@ -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;

View File

@@ -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

View File

@@ -22,6 +22,7 @@
#define NOMINMAX
#include <ObjBase.h>
#include <SDKDDKVer.h>
#include <bcrypt.h>
#include <dwmapi.h>
#include <shellapi.h>
#include <shlwapi.h>

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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 = {};

View File

@@ -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

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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;
}