初始提交: UE5.3项目基础框架

This commit is contained in:
2025-10-14 11:14:54 +08:00
commit 721d9fd98e
5334 changed files with 316782 additions and 0 deletions

View File

@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
//
// Define our own assertion, so that users can forcebly turn them on if needed
//
#if defined CESIUM_FORCE_ASSERTIONS && defined NDEBUG
// Asserts are defined in cassert and normally compiled out when NDEBUG is
// defined. Redirect to our own assertion that can't be compiled out
namespace CesiumUtility {
std::int32_t forceAssertFailure();
};
#define CESIUM_ASSERT(expression) \
((expression) ? 0 : CesiumUtility::forceAssertFailure())
#else
// Let assertions get defined with default behavior
#include <cassert>
#define CESIUM_ASSERT(expression) assert(expression)
#endif

View File

@ -0,0 +1,82 @@
#pragma once
#include "Library.h"
#include "Math.h"
#include <glm/glm.hpp>
namespace CesiumUtility {
/**
* @brief Functions to handle compressed attributes in different formats
*/
class CESIUMUTILITY_API AttributeCompression final {
public:
/**
* @brief Decodes a unit-length vector in 'oct' encoding to a normalized
* 3-component vector.
*
* @param x The x component of the oct-encoded unit length vector.
* @param y The y component of the oct-encoded unit length vector.
* @param rangeMax The maximum value of the SNORM range. The encoded vector is
* stored in log2(rangeMax+1) bits.
* @returns The decoded and normalized vector.
*/
template <
typename T,
class = typename std::enable_if<std::is_unsigned<T>::value>::type>
static glm::dvec3 octDecodeInRange(T x, T y, T rangeMax) {
glm::dvec3 result;
result.x = CesiumUtility::Math::fromSNorm(x, rangeMax);
result.y = CesiumUtility::Math::fromSNorm(y, rangeMax);
result.z = 1.0 - (glm::abs(result.x) + glm::abs(result.y));
if (result.z < 0.0) {
const double oldVX = result.x;
result.x =
(1.0 - glm::abs(result.y)) * CesiumUtility::Math::signNotZero(oldVX);
result.y =
(1.0 - glm::abs(oldVX)) * CesiumUtility::Math::signNotZero(result.y);
}
return glm::normalize(result);
}
/**
* @brief Decodes a unit-length vector in 2 byte 'oct' encoding to a
* normalized 3-component vector.
*
* @param x The x component of the oct-encoded unit length vector.
* @param y The y component of the oct-encoded unit length vector.
* @returns The decoded and normalized vector.
*
* @see AttributeCompression::octDecodeInRange
*/
static glm::dvec3 octDecode(uint8_t x, uint8_t y) {
constexpr uint8_t rangeMax = 255;
return AttributeCompression::octDecodeInRange(x, y, rangeMax);
}
/**
* @brief Decodes a RGB565-encoded color to a 3-component vector
* containing the normalized RGB values.
*
* @param value The RGB565-encoded value.
* @returns The normalized RGB values.
*/
static glm::dvec3 decodeRGB565(const uint16_t value) {
constexpr uint16_t mask5 = (1 << 5) - 1;
constexpr uint16_t mask6 = (1 << 6) - 1;
constexpr float normalize5 = 1.0f / 31.0f; // normalize [0, 31] to [0, 1]
constexpr float normalize6 = 1.0f / 63.0f; // normalize [0, 63] to [0, 1]
const uint16_t red = static_cast<uint16_t>(value >> 11);
const uint16_t green = static_cast<uint16_t>((value >> 5) & mask6);
const uint16_t blue = value & mask5;
return glm::dvec3(red * normalize5, green * normalize6, blue * normalize5);
};
};
} // namespace CesiumUtility

View File

@ -0,0 +1,114 @@
#pragma once
#include "Library.h"
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace CesiumUtility {
/**
* @brief Represents an HTML string that should be shown on screen to attribute
* third parties for used data, imagery, etc. Acts as a handle into a
* {@link CreditSystem} object that actually holds the credit string.
*/
struct CESIUMUTILITY_API Credit {
public:
/**
* @brief Returns `true` if two credit objects have the same ID.
*/
bool operator==(const Credit& rhs) const noexcept {
return this->id == rhs.id;
}
private:
size_t id;
Credit(size_t id_) noexcept { id = id_; }
friend class CreditSystem;
};
/**
* @brief Creates and manages {@link Credit} objects. Avoids repetitions and
* tracks which credits should be shown and which credits should be removed this
* frame.
*/
class CESIUMUTILITY_API CreditSystem final {
public:
/**
* @brief Inserts a credit string
*
* @return If this string already exists, returns a Credit handle to the
* existing entry. Otherwise returns a Credit handle to a new entry.
*/
Credit createCredit(std::string&& html, bool showOnScreen = false);
/**
* @brief Inserts a credit string
*
* @return If this string already exists, returns a Credit handle to the
* existing entry. Otherwise returns a Credit handle to a new entry.
*/
Credit createCredit(const std::string& html, bool showOnScreen = false);
/**
* @brief Gets whether or not the credit should be shown on screen.
*/
bool shouldBeShownOnScreen(Credit credit) const noexcept;
/**
* @brief Sets whether or not the credit should be shown on screen.
*/
void setShowOnScreen(Credit credit, bool showOnScreen) noexcept;
/**
* @brief Get the HTML string for this credit
*/
const std::string& getHtml(Credit credit) const noexcept;
/**
* @brief Adds the Credit to the set of credits to show this frame
*/
void addCreditToFrame(Credit credit);
/**
* @brief Notifies this CreditSystem to start tracking the credits to show for
* the next frame.
*/
void startNextFrame() noexcept;
/**
* @brief Get the credits to show this frame.
*/
const std::vector<Credit>& getCreditsToShowThisFrame() noexcept;
/**
* @brief Get the credits that were shown last frame but should no longer be
* shown.
*/
const std::vector<Credit>&
getCreditsToNoLongerShowThisFrame() const noexcept {
return _creditsToNoLongerShowThisFrame;
}
private:
const std::string INVALID_CREDIT_MESSAGE =
"Error: Invalid Credit, cannot get HTML string.";
struct HtmlAndLastFrameNumber {
std::string html;
bool showOnScreen;
int32_t lastFrameNumber;
int count;
};
std::vector<HtmlAndLastFrameNumber> _credits;
int32_t _currentFrameNumber = 0;
std::vector<Credit> _creditsToShowThisFrame;
std::vector<Credit> _creditsToNoLongerShowThisFrame;
};
} // namespace CesiumUtility

View File

@ -0,0 +1,274 @@
#pragma once
#include <cstddef>
namespace CesiumUtility {
/**
* @brief Contains the previous and next pointers for an element in
* a {@link DoublyLinkedList}.
*/
template <class T> class DoublyLinkedListPointers final {
public:
/**
* @brief Default constructor.
*/
DoublyLinkedListPointers() noexcept : pNext(nullptr), pPrevious(nullptr) {}
/**
* @brief Copy constructor.
*
* @param rhs The other instance.
*/
// Following the example of boost::instrusive::list's list_member_hook, the
// copy constructor and assignment operator do nothing.
// https://www.boost.org/doc/libs/1_73_0/doc/html/boost/intrusive/list_member_hook.html
DoublyLinkedListPointers(
[[maybe_unused]] DoublyLinkedListPointers& rhs) noexcept
: DoublyLinkedListPointers() {}
/**
* @brief Assignment operator.
*/
DoublyLinkedListPointers&
operator=(const DoublyLinkedListPointers& /*rhs*/) noexcept {
return *this;
}
private:
template <
typename TElement,
typename TElementBase,
DoublyLinkedListPointers<TElement>(TElementBase::*Pointers)>
friend class DoublyLinkedListAdvanced;
T* pNext;
T* pPrevious;
};
/**
* @brief A doubly-linked list.
*
* In this implementation, the previous and next pointers are embedded directly
* in the data object.
*
* @tparam T The data object type.
* @tparam (T::*Pointers) A member pointer to the field that holds the links to
* the previous and next nodes.
*/
template <
typename T,
typename TPointerBase,
DoublyLinkedListPointers<T>(TPointerBase::*Pointers)>
class DoublyLinkedListAdvanced final {
public:
/**
* @brief Removes the given node from this list.
*/
void remove(T& node) noexcept {
DoublyLinkedListPointers<T>& nodePointers = node.*Pointers;
if (nodePointers.pPrevious) {
DoublyLinkedListPointers<T>& previousPointers =
nodePointers.pPrevious->*Pointers;
previousPointers.pNext = nodePointers.pNext;
--this->_size;
} else if (this->_pHead == &node) {
this->_pHead = nodePointers.pNext;
--this->_size;
}
if (nodePointers.pNext) {
DoublyLinkedListPointers<T>& nextPointers = nodePointers.pNext->*Pointers;
nextPointers.pPrevious = nodePointers.pPrevious;
} else if (this->_pTail == &node) {
this->_pTail = nodePointers.pPrevious;
}
nodePointers.pPrevious = nullptr;
nodePointers.pNext = nullptr;
}
/**
* @brief Insert the given node after the other node.
*/
void insertAfter(T& after, T& node) noexcept {
this->remove(node);
DoublyLinkedListPointers<T>& afterPointers = after.*Pointers;
DoublyLinkedListPointers<T>& nodePointers = node.*Pointers;
nodePointers.pPrevious = &after;
nodePointers.pNext = afterPointers.pNext;
afterPointers.pNext = &node;
if (nodePointers.pNext) {
DoublyLinkedListPointers<T>& nextPointers = nodePointers.pNext->*Pointers;
nextPointers.pPrevious = &node;
}
if (this->_pTail == &after) {
this->_pTail = &node;
}
++this->_size;
}
/**
* @brief Insert the given node before the other node.
*/
void insertBefore(T& before, T& node) noexcept {
this->remove(node);
DoublyLinkedListPointers<T>& beforePointers = before.*Pointers;
DoublyLinkedListPointers<T>& nodePointers = node.*Pointers;
nodePointers.pPrevious = beforePointers.pPrevious;
nodePointers.pNext = &before;
beforePointers.pPrevious = &node;
if (nodePointers.pPrevious) {
DoublyLinkedListPointers<T>& previousPointers =
nodePointers.pPrevious->*Pointers;
previousPointers.pNext = &node;
}
if (this->_pHead == &before) {
this->_pHead = &node;
}
++this->_size;
}
/**
* @brief Insert the given node as the new head of the list.
*/
void insertAtHead(T& node) noexcept {
this->remove(node);
if (this->_pHead) {
(this->_pHead->*Pointers).pPrevious = &node;
(node.*Pointers).pNext = this->_pHead;
} else {
this->_pTail = &node;
}
this->_pHead = &node;
++this->_size;
}
/**
* @brief Insert the given node as the new tail of the list.
*/
void insertAtTail(T& node) noexcept {
this->remove(node);
if (this->_pTail) {
(this->_pTail->*Pointers).pNext = &node;
(node.*Pointers).pPrevious = this->_pTail;
} else {
this->_pHead = &node;
}
this->_pTail = &node;
++this->_size;
}
/**
* @brief Returns the size of this list.
*/
size_t size() const noexcept { return this->_size; }
/**
* @brief Returns the head node of this list, or `nullptr` if the list is
* empty.
*/
T* head() noexcept { return this->_pHead; }
/** @copydoc DoublyLinkedList::head() */
const T* head() const noexcept { return this->_pHead; }
/**
* @brief Returns the tail node of this list, or `nullptr` if the list is
* empty.
*/
T* tail() noexcept { return this->_pTail; }
/** @copydoc DoublyLinkedList::tail() */
const T* tail() const noexcept { return this->_pTail; }
/**
* @brief Returns the next node after the given one, or `nullptr` if the given
* node is the tail.
*/
T* next(T& node) noexcept { return (node.*Pointers).pNext; }
/** @copydoc DoublyLinkedList::next(T&) */
const T* next(const T& node) const noexcept { return (node.*Pointers).pNext; }
/**
* @brief Returns the next node after the given one, or the head if the given
* node is `nullptr`.
*/
T* next(T* pNode) noexcept {
return pNode ? this->next(*pNode) : this->_pHead;
}
/** @copydoc DoublyLinkedList::next(T*) */
const T* next(const T* pNode) const noexcept {
return pNode ? this->next(*pNode) : this->_pHead;
}
/**
* @brief Returns the previous node before the given one, or `nullptr` if the
* given node is the head.
*/
T* previous(T& node) noexcept { return (node.*Pointers).pPrevious; }
/** @copydoc DoublyLinkedList::previous(T&) */
const T* previous(const T& node) const noexcept {
return (node.*Pointers).pPrevious;
}
/**
* @brief Returns the previous node before the given one, or the tail if the
* given node is `nullptr`.
*/
T* previous(T* pNode) {
return pNode ? this->previous(*pNode) : this->_pTail;
}
/** @copydoc DoublyLinkedList::previous(T*) */
const T* previous(const T* pNode) const noexcept {
return pNode ? this->previous(*pNode) : this->_pTail;
}
/**
* @brief Determines if this list contains a given node in constant time. In
* order to avoid a full list scan, this method assumes that if the node has
* any next or previous node, then it is contained in this list. Do not use
* this method to determine which of multiple lists contain this node.
*
* @param node The node to check.
* @return True if this node is the head of the list, or if the node has next
* or previous nodes. False if the node does not have next or previous nodes
* and it is not the head of this list.
*/
bool contains(const T& node) const {
return this->next(node) != nullptr || this->previous(node) != nullptr ||
this->_pHead == &node;
}
private:
size_t _size = 0;
T* _pHead = nullptr;
T* _pTail = nullptr;
};
/**
* @brief An intrusive doubly-linked list.
*/
template <typename T, DoublyLinkedListPointers<T>(T::*Pointers)>
using DoublyLinkedList = DoublyLinkedListAdvanced<T, T, Pointers>;
} // namespace CesiumUtility

View File

@ -0,0 +1,127 @@
#pragma once
#include "Library.h"
#include "joinToString.h"
#include <spdlog/spdlog.h>
#include <string>
#include <vector>
namespace CesiumUtility {
/**
* @brief The container to store the error and warning list when loading a tile
* or glTF content
*/
struct CESIUMUTILITY_API ErrorList {
/**
* @brief Creates an {@link ErrorList} containing a single error.
*
* @param errorMessage The error message.
* @return The new list containing the single error.
*/
static ErrorList error(std::string errorMessage);
/**
* @brief Creates an {@link ErrorList} containing a single warning.
*
* @param warningMessage The warning message.
* @return The new list containing the single warning.
*/
static ErrorList warning(std::string warningMessage);
/**
* @brief Merge the errors and warnings from other ErrorList together
*
* @param errorList The other instance of ErrorList that will be merged with
* the current instance.
*/
void merge(const ErrorList& errorList);
/**
* @brief Merge the errors and warnings from other ErrorList together
*
* @param errorList The other instance of ErrorList that will be merged with
* the current instance.
*/
void merge(ErrorList&& errorList);
/**
* @brief Add an error message
*
* @param error The error message to be added.
*/
template <typename ErrorStr> void emplaceError(ErrorStr&& error) {
errors.emplace_back(std::forward<ErrorStr>(error));
}
/**
* @brief Add a warning message
*
* @param warning The warning message to be added.
*/
template <typename WarningStr> void emplaceWarning(WarningStr&& warning) {
warnings.emplace_back(std::forward<WarningStr>(warning));
}
/**
* @brief Check if there are any error messages.
*/
bool hasErrors() const noexcept;
/**
* @brief Log all the error messages.
*
* @param pLogger The logger to log the messages
* @param prompt The message prompt for the error messages.
*/
template <typename PromptStr>
void logError(
const std::shared_ptr<spdlog::logger>& pLogger,
PromptStr&& prompt) const noexcept {
if (!errors.empty()) {
SPDLOG_LOGGER_ERROR(
pLogger,
"{}:\n- {}",
std::forward<PromptStr>(prompt),
CesiumUtility::joinToString(errors, "\n- "));
}
}
/**
* @brief Log all the warning messages
*
* @param pLogger The logger to log the messages
* @param prompt The message prompt for the warning messages.
*/
template <typename PromptStr>
void logWarning(
const std::shared_ptr<spdlog::logger>& pLogger,
PromptStr&& prompt) const noexcept {
if (!warnings.empty()) {
SPDLOG_LOGGER_WARN(
pLogger,
"{}:\n- {}",
std::forward<PromptStr>(prompt),
CesiumUtility::joinToString(warnings, "\n- "));
}
}
/**
* @brief Check if there are any error messages.
*/
explicit operator bool() const noexcept;
/**
* @brief The error messages of this container
*/
std::vector<std::string> errors;
/**
* @brief The warning messages of this container
*/
std::vector<std::string> warnings;
};
} // namespace CesiumUtility

View File

@ -0,0 +1,149 @@
#pragma once
#include "JsonValue.h"
#include "Library.h"
#include <any>
#include <unordered_map>
#include <utility>
#include <vector>
namespace CesiumUtility {
/**
* @brief The base class for objects that have extensions and extras.
*/
struct CESIUMUTILITY_API ExtensibleObject {
/**
* @brief Checks if an extension exists given its static type.
*
* @tparam T The type of the extension.
* @return A boolean indicating whether the extension exists.
*/
template <typename T> bool hasExtension() const noexcept {
auto it = this->extensions.find(T::ExtensionName);
return it != this->extensions.end();
}
/**
* @brief Gets an extension given its static type.
*
* @tparam T The type of the extension.
* @return A pointer to the extension, or nullptr if the extension is not
* attached to this object.
*/
template <typename T> const T* getExtension() const noexcept {
auto it = this->extensions.find(T::ExtensionName);
if (it == this->extensions.end()) {
return nullptr;
}
return std::any_cast<T>(&it->second);
}
/** @copydoc ExtensibleObject::getExtension */
template <typename T> T* getExtension() noexcept {
return const_cast<T*>(std::as_const(*this).getExtension<T>());
}
/**
* @brief Gets a generic extension with the given name as a
* {@link CesiumUtility::JsonValue}.
*
* If the extension exists but has a static type, this method will return
* nullptr. Use {@link getExtension} to retrieve a statically-typed extension.
*
* @param extensionName The name of the extension.
* @return The generic extension, or nullptr if the generic extension doesn't
* exist.
*/
const JsonValue*
getGenericExtension(const std::string& extensionName) const noexcept;
/** @copydoc ExtensibleObject::getGenericExtension */
JsonValue* getGenericExtension(const std::string& extensionName) noexcept;
/**
* @brief Adds a statically-typed extension to this object.
*
* If the extension already exists, the existing one is returned.
*
* @tparam T The type of the extension to add.
* @return The added extension.
*/
template <typename T, typename... ConstructorArgumentTypes>
T& addExtension(ConstructorArgumentTypes&&... constructorArguments) {
std::any& extension =
extensions
.try_emplace(
T::ExtensionName,
std::make_any<T>(std::forward<ConstructorArgumentTypes>(
constructorArguments)...))
.first->second;
return std::any_cast<T&>(extension);
}
/**
* @brief Removes a statically-typed extension from this object.
*
* @tparam T The type of the extension to remove.
*/
template <typename T> void removeExtension() {
extensions.erase(T::ExtensionName);
}
/**
* @brief The extensions attached to this object.
*
* Use {@link getExtension} to get the extension with a particular static
* type. Use {@link getGenericExtension} to get unknown extensions as a
* generic {@link CesiumUtility::JsonValue}.
*/
std::unordered_map<std::string, std::any> extensions;
/**
* @brief Application-specific data.
*
* **Implementation Note:** Although extras may have any type, it is common
* for applications to store and access custom data as key/value pairs. As
* best practice, extras should be an Object rather than a primitive value for
* best portability.
*/
JsonValue::Object extras;
/**
* @brief Unknown properties that exist on this object but do not have any
* representation in the statically-typed classes.
*
* These properties may be invalid, or they may represent deprecated,
* experimental, or next-version properties.
*/
JsonValue::Object unknownProperties;
/**
* @brief Calculates the size in bytes of this ExtensibleObject, including all
* of its extras but NOT including its extensions. Calling this method may be
* slow as it requires traversing the entire object.
*/
int64_t getSizeBytes() const {
int64_t accum = 0;
accum += int64_t(sizeof(ExtensibleObject));
accum += int64_t(
this->extras.size() * (sizeof(std::string) + sizeof(JsonValue)));
for (const auto& [k, v] : this->extras) {
accum += int64_t(k.capacity() * sizeof(char) - sizeof(std::string));
accum += v.getSizeBytes() - int64_t(sizeof(JsonValue));
}
accum += int64_t(
this->unknownProperties.size() *
(sizeof(std::string) + sizeof(JsonValue)));
for (const auto& [k, v] : this->unknownProperties) {
accum += int64_t(k.capacity() * sizeof(char) - sizeof(std::string));
accum += v.getSizeBytes() - int64_t(sizeof(JsonValue));
}
return accum;
}
};
} // namespace CesiumUtility

View File

@ -0,0 +1,44 @@
#pragma once
#include <span>
#include <vector>
namespace CesiumUtility {
/**
* @brief Checks whether the data is gzipped.
*
* @param data The data.
*
* @returns Whether the data is gzipped
*/
bool isGzip(const std::span<const std::byte>& data);
/**
* @brief Gzips data.
*
* If successful, it will return true and the result will be in the
* provided vector.
*
* @param data The data to gzip.
* @param out The gzipped data.
*
* @returns True if successful, false otherwise.
*/
bool gzip(const std::span<const std::byte>& data, std::vector<std::byte>& out);
/**
* @brief Gunzips data.
*
* If successful, it will return true and the result will be in the
* provided vector.
*
* @param data The data to gunzip.
* @param out The gunzipped data.
*
* @returns True if successful, false otherwise.
*/
bool gunzip(
const std::span<const std::byte>& data,
std::vector<std::byte>& out);
} // namespace CesiumUtility

View File

@ -0,0 +1,23 @@
#pragma once
#include <cstddef>
namespace CesiumUtility {
/**
* @brief Contains functions for working with hashes.
*
*/
struct Hash {
/**
* @brief Combines two hash values, usually generated using `std::hash`, to
* form a single hash value.
*
* @param first The first hash value.
* @param second The second hash value.
* @return A new hash value which is a combination of the two.
*/
static std::size_t combine(std::size_t first, std::size_t second);
};
} // namespace CesiumUtility

View File

@ -0,0 +1,43 @@
#pragma once
namespace CesiumUtility {
/**
* @brief An interface representing the depot that owns a {@link SharedAsset}.
* This interface is an implementation detail of the shared asset system and
* should not be used directly.
*
* {@link SharedAsset} has a pointer to the asset depot that owns it using this
* interface, rather than a complete {@link CesiumAsync::SharedAssetDepot}, in
* order to "erase" the type of the asset key. This allows SharedAsset to be
* templatized only on the asset type, not on the asset key type.
*/
template <typename TAssetType> class IDepotOwningAsset {
public:
virtual ~IDepotOwningAsset() {}
/**
* @brief Marks the given asset as a candidate for deletion.
* Should only be called by {@link SharedAsset}. May be called from any thread.
*
* @param asset The asset to mark for deletion.
* @param threadOwnsDepotLock True if the calling thread already owns the
* depot lock; otherwise, false.
*/
virtual void
markDeletionCandidate(const TAssetType& asset, bool threadOwnsDepotLock) = 0;
/**
* @brief Unmarks the given asset as a candidate for deletion.
* Should only be called by {@link SharedAsset}. May be called from any thread.
*
* @param asset The asset to unmark for deletion.
* @param threadOwnsDepotLock True if the calling thread already owns the
* depot lock; otherwise, false.
*/
virtual void unmarkDeletionCandidate(
const TAssetType& asset,
bool threadOwnsDepotLock) = 0;
};
} // namespace CesiumUtility

View File

@ -0,0 +1,241 @@
#pragma once
#include <type_traits>
#include <utility>
namespace CesiumUtility {
/**
* @brief A smart pointer that calls `addReference` and `releaseReference` on
* the controlled object.
*
* Please note that the thread-safety of this type is entirely dependent on the
* implementation of `addReference` and `releaseReference`. If these methods are
* not thread safe on a particular type - which is common for objects that are
* not meant to be used from multiple threads simultaneously - then using an
* `IntrusivePointer` from multiple threads is also unsafe.
*
* @tparam T The type of object controlled.
*/
template <class T> class IntrusivePointer final {
public:
/**
* @brief Default constructor.
*/
IntrusivePointer(T* p = nullptr) noexcept : _p(p) { this->addReference(); }
/**
* @brief Copy constructor.
*/
IntrusivePointer(const IntrusivePointer<T>& rhs) noexcept : _p(rhs._p) {
this->addReference();
}
/**
* @brief Implicit conversion to a pointer to a base (or otherwise
* convertible) type.
*
* @tparam U The new type, usually a base class.
* @param rhs The pointer.
*/
template <class U>
IntrusivePointer(const IntrusivePointer<U>& rhs) noexcept : _p(rhs._p) {
this->addReference();
}
/**
* @brief Move constructor.
*/
IntrusivePointer(IntrusivePointer<T>&& rhs) noexcept
: _p(std::exchange(rhs._p, nullptr)) {
// Reference count is unchanged
}
/**
* @brief Implicit conversion of an r-value to a pointer to a base (or
* otherwise convertible) type.
*
* @tparam U The new type, usually a base class.
* @param rhs The pointer.
*/
template <class U>
IntrusivePointer(IntrusivePointer<U>&& rhs) noexcept
: _p(std::exchange(rhs._p, nullptr)) {
// Reference count is unchanged
}
/**
* @brief Default destructor.
*/
~IntrusivePointer() noexcept { this->releaseReference(); }
/**
* @brief Constructs a new instance and assigns it to this IntrusivePointer.
* If this IntrusivePointer already points to another instance,
* {@link releaseReference} is called on it.
*
* @param constructorArguments The arguments to the constructor to create the
* new instance.
* @return A reference to the newly-created instance.
*/
template <typename... ConstructorArgumentTypes>
T& emplace(ConstructorArgumentTypes&&... constructorArguments) {
*this =
new T(std::forward<ConstructorArgumentTypes>(constructorArguments)...);
return *this->get();
}
/**
* @brief Reset this pointer to nullptr.
*/
void reset() { *this = nullptr; }
/**
* @brief Assignment operator.
*/
IntrusivePointer& operator=(const IntrusivePointer& rhs) noexcept {
if (this->_p != rhs._p) {
// addReference the new pointer before releaseReference'ing the old.
T* pOld = this->_p;
this->_p = rhs._p;
addReference();
if (pOld) {
pOld->releaseReference();
}
}
return *this;
}
/**
* @brief Assigns an \ref IntrusivePointer of another type to this \ref
* IntrusivePointer.
*/
template <class U>
IntrusivePointer& operator=(const IntrusivePointer<U>& rhs) noexcept {
if (this->_p != rhs._p) {
// addReference the new pointer before releaseReference'ing the old.
T* pOld = this->_p;
this->_p = rhs._p;
addReference();
if (pOld) {
pOld->releaseReference();
}
}
return *this;
}
/**
* @brief Move assignment operator.
*/
IntrusivePointer& operator=(IntrusivePointer&& rhs) noexcept {
if (this->_p != rhs._p) {
std::swap(this->_p, rhs._p);
}
return *this;
}
/**
* @brief Assignment operator.
*/
IntrusivePointer& operator=(T* p) noexcept {
if (this->_p != p) {
// addReference the new pointer before releaseReference'ing the old.
T* pOld = this->_p;
this->_p = p;
addReference();
if (pOld) {
pOld->releaseReference();
}
}
return *this;
}
/**
* @brief Dereferencing operator.
*/
T& operator*() const noexcept { return *this->_p; }
/**
* @brief Arrow operator.
*/
T* operator->() const noexcept { return this->_p; }
/**
* @brief Implicit conversion to `bool`, being `true` iff this is not the
* `nullptr`.
*/
explicit operator bool() const noexcept { return this->_p != nullptr; }
/**
* @brief Returns the internal pointer.
*/
T* get() const noexcept { return this->_p; }
/**
* @brief Returns `true` if two pointers are equal.
*/
bool operator==(const IntrusivePointer<T>& rhs) const noexcept {
return this->_p == rhs._p;
}
/** @brief Returns `true` if two pointers are equal. */
template <class U>
bool operator==(const IntrusivePointer<U>& rhs) const noexcept {
return this->_p == rhs._p;
}
/**
* @brief Returns `true` if two pointers are *not* equal.
*/
bool operator!=(const IntrusivePointer<T>& rhs) const noexcept {
return !(*this == rhs);
}
/**
* @brief Returns `true` if the contents of this pointer is equal to the given
* pointer.
*/
bool operator==(const T* pRhs) const noexcept { return this->_p == pRhs; }
/**
* @brief Returns `true` if the contents of this pointer is *not* equal to the
* given pointer.
*/
bool operator!=(const T* pRhs) const noexcept { return !(*this == pRhs); }
private:
void addReference() noexcept {
if (this->_p) {
this->_p->addReference();
}
}
void releaseReference() noexcept {
if (this->_p) {
this->_p->releaseReference();
}
}
T* _p;
template <typename U> friend class IntrusivePointer;
};
/**
* @brief Casts a `const` \ref IntrusivePointer to its non-const equivalent.
*
* @param p The `const` \ref IntrusivePointer.
* @returns A non-const \ref IntrusivePointer with the same underlying pointer.
*/
template <typename T, typename U>
IntrusivePointer<T>
const_intrusive_cast(const IntrusivePointer<U>& p) noexcept {
return IntrusivePointer<T>(const_cast<T*>(p.get()));
}
} // namespace CesiumUtility

View File

@ -0,0 +1,246 @@
#pragma once
#include <glm/fwd.hpp>
#include <rapidjson/fwd.h>
#include <optional>
#include <string>
#include <vector>
namespace CesiumUtility {
/**
* @brief A collection of helper functions to make reading JSON simpler.
*/
class JsonHelpers final {
public:
/**
* @brief Attempts to read the value at `key` of `tileJson` as a `double`,
* returning std::nullopt if it wasn't found or couldn't be read as a double.
*
* @param tileJson The JSON object to obtain the scalar property from.
* @param key The key of the scalar property to obtain.
*/
static std::optional<double>
getScalarProperty(const rapidjson::Value& tileJson, const std::string& key);
/**
* @brief Attempts to read the value at `key` of `tileJson` as a
* `glm::dmat4x4`, returning std::nullopt if it wasn't found or couldn't be
* read as a glm::dmat4x4.
*
* @param tileJson The JSON object to obtain the transform property from.
* @param key The key of the transform property.
*/
static std::optional<glm::dmat4x4> getTransformProperty(
const rapidjson::Value& tileJson,
const std::string& key);
/**
* @brief Obtains an array of numbers from the given JSON.
*
* If the property is not found, or is not an array, or does contain
* elements that are not numbers, then `std::nullopt` is returned.
*
* If the given expected size is not negative, and the actual size of the
* array does not match the expected size, then `nullopt` is returned.
*
* @param json The JSON object.
* @param expectedSize The expected size of the array.
* @param key The key (property name) of the array.
* @return The array, or `nullopt`.
*/
static std::optional<std::vector<double>> getDoubles(
const rapidjson::Value& json,
int32_t expectedSize,
const std::string& key);
/**
* @brief Attempts to obtain a string from the given key on the JSON object,
* returning a default value if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the string.
* @param defaultValue The default value to return if the string property
* `key` of `json` couldn't be read.
*/
static std::string getStringOrDefault(
const rapidjson::Value& json,
const std::string& key,
const std::string& defaultValue);
/**
* @brief Attempts to read `json` as a string, returning a default value if
* this isn't possible.
*
* @param json The JSON value that might be a string.
* @param defaultValue The default value to return if `json` couldn't be read
* as a string.
*/
static std::string getStringOrDefault(
const rapidjson::Value& json,
const std::string& defaultValue);
/**
* @brief Attempts to obtain a double from the given key on the JSON object,
* returning a default value if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the double.
* @param defaultValue The default value to return if the double property
* `key` of `json` couldn't be read.
*/
static double getDoubleOrDefault(
const rapidjson::Value& json,
const std::string& key,
double defaultValue);
/**
* @brief Attempts to read `json` as a double, returning a default value if
* this isn't possible.
*
* @param json The JSON value that might be a double.
* @param defaultValue The default value to return if `json` couldn't be read
* as a double.
*/
static double
getDoubleOrDefault(const rapidjson::Value& json, double defaultValue);
/**
* @brief Attempts to obtain a uint32_t from the given key on the JSON object,
* returning a default value if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the uint32_t.
* @param defaultValue The default value to return if the uint32_t property
* `key` of `json` couldn't be read.
*/
static uint32_t getUint32OrDefault(
const rapidjson::Value& json,
const std::string& key,
uint32_t defaultValue);
/**
* @brief Attempts to read `json` as a uint32_t, returning a default value if
* this isn't possible.
*
* @param json The JSON value that might be a uint32_t.
* @param defaultValue The default value to return if `json` couldn't be read
* as a uint32_t.
*/
static uint32_t
getUint32OrDefault(const rapidjson::Value& json, uint32_t defaultValue);
/**
* @brief Attempts to obtain a int32_t from the given key on the JSON object,
* returning a default value if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the int32_t.
* @param defaultValue The default value to return if the int32_t property
* `key` of `json` couldn't be read.
*/
static int32_t getInt32OrDefault(
const rapidjson::Value& json,
const std::string& key,
int32_t defaultValue);
/**
* @brief Attempts to read `json` as a int32_t, returning a default value if
* this isn't possible.
*
* @param json The JSON value that might be a int32_t.
* @param defaultValue The default value to return if `json` couldn't be read
* as a int32_t.
*/
static int32_t
getInt32OrDefault(const rapidjson::Value& json, int32_t defaultValue);
/**
* @brief Attempts to obtain a uint64_t from the given key on the JSON object,
* returning a default value if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the uint64_t.
* @param defaultValue The default value to return if the uint64_t property
* `key` of `json` couldn't be read.
*/
static uint64_t getUint64OrDefault(
const rapidjson::Value& json,
const std::string& key,
uint64_t defaultValue);
/**
* @brief Attempts to read `json` as a uint64_t, returning a default value if
* this isn't possible.
*
* @param json The JSON value that might be a uint64_t.
* @param defaultValue The default value to return if `json` couldn't be read
* as a uint64_t.
*/
static uint64_t
getUint64OrDefault(const rapidjson::Value& json, uint64_t defaultValue);
/**
* @brief Attempts to obtain a int64_t from the given key on the JSON object,
* returning a default value if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the int64_t.
* @param defaultValue The default value to return if the int64_t property
* `key` of `json` couldn't be read.
*/
static int64_t getInt64OrDefault(
const rapidjson::Value& json,
const std::string& key,
int64_t defaultValue);
/**
* @brief Attempts to read `json` as a int64_t, returning a default value if
* this isn't possible.
*
* @param json The JSON value that might be a int64_t.
* @param defaultValue The default value to return if `json` couldn't be read
* as a int64_t.
*/
static int64_t
getInt64OrDefault(const rapidjson::Value& json, int64_t defaultValue);
/**
* @brief Attempts to obtain a bool from the given key on the JSON object,
* returning a default value if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the bool.
* @param defaultValue The default value to return if the bool property
* `key` of `json` couldn't be read.
*/
static bool getBoolOrDefault(
const rapidjson::Value& json,
const std::string& key,
bool defaultValue);
/**
* @brief Attempts to read `json` as a bool, returning a default value if
* this isn't possible.
*
* @param json The JSON value that might be a bool.
* @param defaultValue The default value to return if `json` couldn't be read
* as a bool.
*/
static bool getBoolOrDefault(const rapidjson::Value& json, bool defaultValue);
/**
* @brief Attempts to read an array of strings from the property `key` of
* `json`, returning an empty vector if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the string array.
*/
static std::vector<std::string>
getStrings(const rapidjson::Value& json, const std::string& key);
/**
* @brief Attempts to read an int64_t array from the property `key` of
* `json`, returning an empty vector if this isn't possible.
*
* @param json The JSON object.
* @param key The key (property name) of the int64_t array.
*/
static std::vector<int64_t>
getInt64s(const rapidjson::Value& json, const std::string& key);
};
} // namespace CesiumUtility

View File

@ -0,0 +1,712 @@
#pragma once
#include "Library.h"
#include <cmath>
#include <cstdint>
#include <initializer_list>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
namespace CesiumUtility {
/**
* @brief Attempts a narrowing conversion of `U` into `T` without losing
* information. If a lossless conversion can't be performed, `std::nullopt` is
* returned.
*
* @tparam U The type to convert from.
* @tparam T The type to convert to.
* @param u The value to perform the conversion on.
*/
template <typename T, typename U>
constexpr std::optional<T> losslessNarrow(U u) noexcept {
constexpr const bool is_different_signedness =
(std::is_signed<T>::value != std::is_signed<U>::value);
const T t = static_cast<T>(u);
if (static_cast<U>(t) != u ||
(is_different_signedness && ((t < T{}) != (u < U{})))) {
return std::nullopt;
}
return t;
}
/**
* @brief Attempts a narrowing conversion of `U` into `T` without losing
* information. If a lossless conversion can't be performed, `defaultValue` is
* returned.
*
* @tparam U The type to convert from.
* @tparam T The type to convert to.
* @param u The value to perform the conversion on.
* @param defaultValue The value that will be returned if a lossless conversion
* can't be performed.
*/
template <typename T, typename U>
constexpr T losslessNarrowOrDefault(U u, T defaultValue) noexcept {
constexpr const bool is_different_signedness =
(std::is_signed<T>::value != std::is_signed<U>::value);
const T t = static_cast<T>(u);
if (static_cast<U>(t) != u ||
(is_different_signedness && ((t < T{}) != (u < U{})))) {
return defaultValue;
}
return t;
}
/**
* @brief A generic implementation of a value in a JSON structure.
*
* Instances of this class are used to represent the common `extras` field
* of glTF elements that extend the the {@link ExtensibleObject} class.
*/
class CESIUMUTILITY_API JsonValue final {
public:
/**
* @brief The type to represent a `null` JSON value.
*/
using Null = std::nullptr_t;
/**
* @brief The type to represent a `Bool` JSON value.
*/
using Bool = bool;
/**
* @brief The type to represent a `String` JSON value.
*/
using String = std::string;
/**
* @brief The type to represent an `Object` JSON value.
*/
using Object = std::map<std::string, JsonValue>;
/**
* @brief The type to represent an `Array` JSON value.
*/
using Array = std::vector<JsonValue>;
/**
* @brief Default constructor.
*/
JsonValue() noexcept : value() {}
/**
* @brief Creates a `null` JSON value.
*/
JsonValue(std::nullptr_t) noexcept : value(nullptr) {}
/**
* @brief Creates a `Number` JSON value.
*
* NaN and ±Infinity are represented as {@link JsonValue::Null}.
*/
JsonValue(double v) noexcept {
if (std::isnan(v) || std::isinf(v)) {
value = nullptr;
} else {
value = v;
}
}
/**
* @brief Creates a `std::int64_t` JSON value (Widening conversion from
* std::int8_t).
*/
JsonValue(std::int8_t v) noexcept : value(static_cast<std::int64_t>(v)) {}
/**
* @brief Creates a `std::uint64_t` JSON value (Widening conversion from
* std::uint8_t).
*/
JsonValue(std::uint8_t v) noexcept : value(static_cast<std::uint64_t>(v)) {}
/**
* @brief Creates a `std::int64_t` JSON value (Widening conversion from
* std::int16_t).
*/
JsonValue(std::int16_t v) noexcept : value(static_cast<std::int64_t>(v)) {}
/**
* @brief Creates a `std::uint64_t` JSON value (Widening conversion from
* std::uint16_t).
*/
JsonValue(std::uint16_t v) noexcept : value(static_cast<std::uint64_t>(v)) {}
/**
* @brief Creates a `std::int64_t` JSON value (Widening conversion from
* std::int32_t).
*/
JsonValue(std::int32_t v) noexcept : value(static_cast<std::int64_t>(v)) {}
/**
* @brief Creates a `std::uint64_t` JSON value (Widening conversion from
* std::uint32_t).
*/
JsonValue(std::uint32_t v) noexcept : value(static_cast<std::uint64_t>(v)) {}
/**
* @brief Creates a `std::int64_t` JSON value.
*/
JsonValue(std::int64_t v) noexcept : value(v) {}
/**
* @brief Creates a `std::uint64_t` JSON value.
*/
JsonValue(std::uint64_t v) noexcept : value(v) {}
/**
* @brief Creates a `Bool` JSON value.
*/
JsonValue(bool v) noexcept : value(v) {}
/**
* @brief Creates a `String` JSON value.
*/
JsonValue(const std::string& v) : value(v) {}
/**
* @brief Creates a `String` JSON value.
*/
JsonValue(std::string&& v) noexcept : value(std::move(v)) {}
/**
* @brief Creates a `String` JSON value.
*/
JsonValue(const char* v) : value(std::string(v)) {}
/**
* @brief Creates an `Object` JSON value with the given properties.
*/
JsonValue(const std::map<std::string, JsonValue>& v) : value(v) {}
/**
* @brief Creates an `Object` JSON value with the given properties.
*/
JsonValue(std::map<std::string, JsonValue>&& v) : value(std::move(v)) {}
/**
* @brief Creates an `Array` JSON value with the given elements.
*/
JsonValue(const std::vector<JsonValue>& v) : value(v) {}
/**
* @brief Creates an `Array` JSON value with the given elements.
*/
JsonValue(std::vector<JsonValue>&& v) noexcept : value(std::move(v)) {}
/**
* @brief Creates an JSON value from the given initializer list.
*/
JsonValue(std::initializer_list<JsonValue> v)
: value(std::vector<JsonValue>(v)) {}
/**
* @brief Creates an JSON value from the given initializer list.
*/
JsonValue(std::initializer_list<std::pair<const std::string, JsonValue>> v)
: value(std::map<std::string, JsonValue>(v)) {}
/**
* @brief Attempts to obtain a pointer to a \ref JsonValue for the given key
* on this object.
*
* @param key The key to lookup.
* @returns A pointer to the \ref JsonValue for the provided key, or
* `nullptr`.
*/
[[nodiscard]] const JsonValue*
getValuePtrForKey(const std::string& key) const;
/** @copydoc getValuePtrForKey */
[[nodiscard]] JsonValue* getValuePtrForKey(const std::string& key);
/**
* @brief Gets a typed value corresponding to the given key in the
* object represented by this instance.
*
* If this instance is not a {@link JsonValue::Object}, returns
* `nullptr`. If the key does not exist in this object, returns
* `nullptr`. If the named value does not have the type T, returns
* nullptr.
*
* @tparam T The expected type of the value.
* @param key The key for which to retrieve the value from this object.
* @return A pointer to the requested value, or nullptr if the value
* cannot be obtained as requested.
*/
template <typename T>
const T* getValuePtrForKey(const std::string& key) const {
const JsonValue* pValue = this->getValuePtrForKey(key);
if (!pValue) {
return nullptr;
}
return std::get_if<T>(&pValue->value);
}
/**
* @brief Gets a typed value corresponding to the given key in the
* object represented by this instance.
*
* If this instance is not a {@link JsonValue::Object}, returns
* `nullptr`. If the key does not exist in this object, returns
* `nullptr`. If the named value does not have the type T, returns
* nullptr.
*
* @tparam T The expected type of the value.
* @param key The key for which to retrieve the value from this object.
* @return A pointer to the requested value, or nullptr if the value
* cannot be obtained as requested.
*/
template <typename T> T* getValuePtrForKey(const std::string& key) {
JsonValue* pValue = this->getValuePtrForKey(key);
return std::get_if<T>(&pValue->value);
}
/**
* @brief Converts the numerical value corresponding to the given key
* to the provided numerical template type.
* If this instance is not a {@link JsonValue::Object}, the key does not exist
* in this object, the named value does not have a numerical type, or if the
* named value cannot be converted from `double` / `std::uint64_t` /
* `std::int64_t` without precision loss, returns `std::nullopt`.
* @tparam To The expected type of the value.
* @param key The key for which to retrieve the value from this object.
* @return The converted value, or std::nullopt if it cannot be converted for
* any of the previously-mentioned reasons.
* @remarks Compilation will fail if type 'To' is not an integral / float /
* double type.
*/
template <
typename To,
typename std::enable_if<
std::is_integral<To>::value ||
std::is_floating_point<To>::value>::type* = nullptr>
[[nodiscard]] std::optional<To>
getSafeNumericalValueForKey(const std::string& key) const {
const Object& pObject = std::get<Object>(this->value);
const auto it = pObject.find(key);
if (it == pObject.end()) {
return std::nullopt;
}
return it->second.getSafeNumber<To>();
}
/**
* @brief Converts the numerical value corresponding to the given key
* to the provided numerical template type.
*
* If this instance is not a {@link JsonValue::Object}, the key does not exist
* in this object, or the named value does not have a numerical type that can
* be represented as T without precision loss, then the default value is
* returned.
*
* @tparam To The expected type of the value.
* @param key The key for which to retrieve the value from this object.
* @param defaultValue The value that will be returned if a numerical value
* can't be obtained.
* @return The converted value.
* @throws If unable to convert the converted value for one of the
* aforementioned reasons.
* @remarks Compilation will fail if type 'To' is not an integral / float /
* double type.
*/
template <
typename To,
typename std::enable_if<
std::is_integral<To>::value ||
std::is_floating_point<To>::value>::type* = nullptr>
[[nodiscard]] To getSafeNumericalValueOrDefaultForKey(
const std::string& key,
To defaultValue) const {
const Object& pObject = std::get<Object>(this->value);
const auto it = pObject.find(key);
if (it == pObject.end()) {
return defaultValue;
}
return it->second.getSafeNumberOrDefault<To>(defaultValue);
}
/**
* @brief Determines if this value is an Object and has the given key.
*
* @param key The key.
* @return true if this value contains the key. false if it is not an object
* or does not contain the given key.
*/
[[nodiscard]] inline bool hasKey(const std::string& key) const {
const Object* pObject = std::get_if<Object>(&this->value);
if (!pObject) {
return false;
}
return pObject->find(key) != pObject->end();
}
/**
* @brief Gets the numerical quantity from the value casted to the `To`
* type. This function should be used over `getDouble()` / `getUint64()` /
* `getInt64()` if you plan on casting that type into another smaller type or
* different type.
* @returns The converted type if it is numerical and it can be cast without
* precision loss; otherwise, std::nullopt.
*/
template <
typename To,
typename std::enable_if<
std::is_integral<To>::value ||
std::is_floating_point<To>::value>::type* = nullptr>
[[nodiscard]] std::optional<To> getSafeNumber() const {
const std::uint64_t* uInt = std::get_if<std::uint64_t>(&this->value);
if (uInt) {
return losslessNarrow<To>(*uInt);
}
const std::int64_t* sInt = std::get_if<std::int64_t>(&this->value);
if (sInt) {
return losslessNarrow<To>(*sInt);
}
const double* real = std::get_if<double>(&this->value);
if (real) {
return losslessNarrow<To>(*real);
}
return std::nullopt;
}
/**
* @brief Gets the numerical quantity from the value casted to the `To`
* type or returns defaultValue if unable to do so.
* @returns The converted type if it can be cast without precision loss
* or `defaultValue` if it cannot be converted safely.
*/
template <
typename To,
typename std::enable_if<
std::is_integral<To>::value ||
std::is_floating_point<To>::value>::type* = nullptr>
[[nodiscard]] To getSafeNumberOrDefault(To defaultValue) const noexcept {
const std::uint64_t* uInt = std::get_if<std::uint64_t>(&this->value);
if (uInt) {
return losslessNarrowOrDefault<To>(*uInt, defaultValue);
}
const std::int64_t* sInt = std::get_if<std::int64_t>(&this->value);
if (sInt) {
return losslessNarrowOrDefault<To>(*sInt, defaultValue);
}
const double* real = std::get_if<double>(&this->value);
if (real) {
return losslessNarrowOrDefault<To>(*real, defaultValue);
}
return defaultValue;
}
/**
* @brief Gets the object from the value.
* @return The object.
* @throws std::bad_variant_access if the underlying type is not a
* JsonValue::Object
*/
[[nodiscard]] inline const JsonValue::Object& getObject() const {
return std::get<JsonValue::Object>(this->value);
}
/**
* @brief Gets the string from the value.
* @return The string.
* @throws std::bad_variant_access if the underlying type is not a
* JsonValue::String
*/
[[nodiscard]] inline const JsonValue::String& getString() const {
return std::get<String>(this->value);
}
/**
* @brief Gets the array from the value.
* @return The array.
* @throws std::bad_variant_access if the underlying type is not a
* JsonValue::Array
*/
[[nodiscard]] inline const JsonValue::Array& getArray() const {
return std::get<JsonValue::Array>(this->value);
}
/**
* @brief Gets an array of strings from the value.
*
* @param defaultString The default string to include in the array for an
* element that is not a string.
* @return The array of strings, or an empty array if this value is not an
* array at all.
*/
[[nodiscard]] std::vector<std::string>
getArrayOfStrings(const std::string& defaultString) const;
/**
* @brief Gets the bool from the value.
* @return The bool.
* @throws std::bad_variant_access if the underlying type is not a
* JsonValue::Bool
*/
[[nodiscard]] inline bool getBool() const {
return std::get<bool>(this->value);
}
/**
* @brief Gets the double from the value.
* @return The double.
* @throws std::bad_variant_access if the underlying type is not a double
*/
[[nodiscard]] inline double getDouble() const {
return std::get<double>(this->value);
}
/**
* @brief Gets the std::uint64_t from the value.
* @return The std::uint64_t.
* @throws std::bad_variant_access if the underlying type is not a
* std::uint64_t
*/
[[nodiscard]] std::uint64_t getUint64() const {
return std::get<std::uint64_t>(this->value);
}
/**
* @brief Gets the std::int64_t from the value.
* @return The std::int64_t.
* @throws std::bad_variant_access if the underlying type is not a
* std::int64_t
*/
[[nodiscard]] std::int64_t getInt64() const {
return std::get<std::int64_t>(this->value);
}
/**
* @brief Gets the bool from the value or returns defaultValue
* @return The bool or defaultValue if this->value is not a bool.
*/
[[nodiscard]] inline bool getBoolOrDefault(bool defaultValue) const noexcept {
const auto* v = std::get_if<bool>(&this->value);
if (v) {
return *v;
}
return defaultValue;
}
/**
* @brief Gets the string from the value or returns defaultValue
* @return The string or defaultValue if this->value is not a string.
*/
[[nodiscard]] inline const JsonValue::String
getStringOrDefault(String defaultValue) const {
const auto* v = std::get_if<JsonValue::String>(&this->value);
if (v) {
return *v;
}
return defaultValue;
}
/**
* @brief Gets the double from the value or returns defaultValue
* @return The double or defaultValue if this->value is not a double.
*/
[[nodiscard]] inline double
getDoubleOrDefault(double defaultValue) const noexcept {
const auto* v = std::get_if<double>(&this->value);
if (v) {
return *v;
}
return defaultValue;
}
/**
* @brief Gets the uint64_t from the value or returns defaultValue
* @return The uint64_t or defaultValue if this->value is not a uint64_t.
*/
[[nodiscard]] inline std::uint64_t
getUint64OrDefault(std::uint64_t defaultValue) const noexcept {
const auto* v = std::get_if<std::uint64_t>(&this->value);
if (v) {
return *v;
}
return defaultValue;
}
/**
* @brief Gets the int64_t from the value or returns defaultValue
* @return The int64_t or defaultValue if this->value is not a int64_t.
*/
[[nodiscard]] inline std::int64_t
getInt64OrDefault(std::int64_t defaultValue) const noexcept {
const auto* v = std::get_if<std::int64_t>(&this->value);
if (v) {
return *v;
}
return defaultValue;
}
/**
* @brief Returns whether this value is a `null` value.
*/
[[nodiscard]] inline bool isNull() const noexcept {
return std::holds_alternative<Null>(this->value);
}
/**
* @brief Returns whether this value is a `double`, `std::uint64_t` or
* `std::int64_t`. Use this function in conjunction with `getNumber` for
* safely casting to arbitrary types
*/
[[nodiscard]] inline bool isNumber() const noexcept {
return isDouble() || isUint64() || isInt64();
}
/**
* @brief Returns whether this value is a `Bool` value.
*/
[[nodiscard]] inline bool isBool() const noexcept {
return std::holds_alternative<Bool>(this->value);
}
/**
* @brief Returns whether this value is a `String` value.
*/
[[nodiscard]] inline bool isString() const noexcept {
return std::holds_alternative<String>(this->value);
}
/**
* @brief Returns whether this value is an `Object` value.
*/
[[nodiscard]] inline bool isObject() const noexcept {
return std::holds_alternative<Object>(this->value);
}
/**
* @brief Returns whether this value is an `Array` value.
*/
[[nodiscard]] inline bool isArray() const noexcept {
return std::holds_alternative<Array>(this->value);
}
/**
* @brief Returns whether this value is a `double` value.
*/
[[nodiscard]] inline bool isDouble() const noexcept {
return std::holds_alternative<double>(this->value);
}
/**
* @brief Returns whether this value is a `std::uint64_t` value.
*/
[[nodiscard]] inline bool isUint64() const noexcept {
return std::holds_alternative<std::uint64_t>(this->value);
}
/**
* @brief Returns whether this value is a `std::int64_t` value.
*/
[[nodiscard]] inline bool isInt64() const noexcept {
return std::holds_alternative<std::int64_t>(this->value);
}
/**
* @brief Returns `true` if two values are equal.
*/
inline bool operator==(const JsonValue& rhs) const noexcept {
return this->value == rhs.value;
};
/**
* @brief Returns the size in bytes of this `JsonValue`.
*/
int64_t getSizeBytes() const noexcept {
struct Operation {
int64_t operator()([[maybe_unused]] const Null& /*inside*/) { return 0; }
int64_t operator()([[maybe_unused]] const double& /*inside*/) {
return 0;
}
int64_t operator()([[maybe_unused]] const std::uint64_t& /*inside*/) {
return 0;
}
int64_t operator()([[maybe_unused]] const std::int64_t& /*inside*/) {
return 0;
}
int64_t operator()([[maybe_unused]] const Bool& /*inside*/) { return 0; }
int64_t operator()(const String& inside) {
return int64_t(inside.capacity() * sizeof(char));
}
int64_t operator()(const Object& inside) {
int64_t accum = 0;
accum +=
int64_t(inside.size() * (sizeof(std::string) + sizeof(JsonValue)));
for (const auto& [k, v] : inside) {
accum += int64_t(k.capacity() * sizeof(char) - sizeof(std::string));
accum += v.getSizeBytes() - int64_t(sizeof(JsonValue));
}
return accum;
}
int64_t operator()(const Array& inside) {
int64_t accum = 0;
accum += int64_t(sizeof(JsonValue) * inside.capacity());
for (const JsonValue& v : inside) {
accum += v.getSizeBytes() - int64_t(sizeof(JsonValue));
}
return accum;
}
};
return static_cast<int64_t>(sizeof(JsonValue)) +
std::visit(Operation{}, this->value);
}
/**
* @brief The actual value.
*
* The type of the value may be queried with the `isNull`, `isDouble`,
* `isBool`, `isString`, `isObject`, `isUint64`, `isInt64`, `isNumber`, and
* `isArray` functions.
*
* The actual value may be obtained with the `getNumber`, `getBool`,
* and `getString` functions for the respective types. For
* `Object` values, the properties may be accessed with the
* `getValueForKey` functions.
*/
std::variant<
Null,
double,
std::uint64_t,
std::int64_t,
Bool,
String,
Object,
Array>
value;
};
} // namespace CesiumUtility

View File

@ -0,0 +1,18 @@
#pragma once
/**
* @brief Utility classes for Cesium
*
* @mermaid-interactive{dependencies/CesiumUtility}
*/
namespace CesiumUtility {}
#if defined(_WIN32) && defined(CESIUM_SHARED)
#ifdef CESIUMUTILITY_BUILDING
#define CESIUMUTILITY_API __declspec(dllexport)
#else
#define CESIUMUTILITY_API __declspec(dllimport)
#endif
#else
#define CESIUMUTILITY_API
#endif

View File

@ -0,0 +1,73 @@
#pragma once
#include <rapidjson/document.h>
#include <spdlog/fmt/fmt.h>
/** @cond Doxygen_Exclude */
template <>
struct fmt::formatter<rapidjson::ParseErrorCode> : formatter<string_view> {
// parse is inherited from formatter<string_view>.
auto format(rapidjson::ParseErrorCode code, format_context& ctx) const {
string_view name = "unknown";
switch (code) {
case rapidjson::ParseErrorCode::kParseErrorNone:
name = "No error.";
break;
case rapidjson::ParseErrorCode::kParseErrorDocumentEmpty:
name = "The document is empty.";
break;
case rapidjson::ParseErrorCode::kParseErrorDocumentRootNotSingular:
name = "The document root must not follow by other values.";
break;
case rapidjson::ParseErrorCode::kParseErrorValueInvalid:
name = "Invalid value.";
break;
case rapidjson::ParseErrorCode::kParseErrorObjectMissName:
name = "Missing a name for object member.";
break;
case rapidjson::ParseErrorCode::kParseErrorObjectMissColon:
name = "Missing a colon after a name of object member.";
break;
case rapidjson::ParseErrorCode::kParseErrorObjectMissCommaOrCurlyBracket:
name = "Missing a comma or '}' after an object member.";
break;
case rapidjson::ParseErrorCode::kParseErrorArrayMissCommaOrSquareBracket:
name = "Missing a comma or ']' after an array element.";
break;
case rapidjson::ParseErrorCode::kParseErrorStringUnicodeEscapeInvalidHex:
name = "Incorrect hex digit after \\u escape in string.";
break;
case rapidjson::ParseErrorCode::kParseErrorStringUnicodeSurrogateInvalid:
name = "The surrogate pair in string is invalid.";
break;
case rapidjson::ParseErrorCode::kParseErrorStringEscapeInvalid:
name = "Invalid escape character in string.";
break;
case rapidjson::ParseErrorCode::kParseErrorStringMissQuotationMark:
name = "Missing a closing quotation mark in string.";
break;
case rapidjson::ParseErrorCode::kParseErrorStringInvalidEncoding:
name = "Invalid encoding in string.";
break;
case rapidjson::ParseErrorCode::kParseErrorNumberTooBig:
name = "Number too big to be stored in double.";
break;
case rapidjson::ParseErrorCode::kParseErrorNumberMissFraction:
name = "Miss fraction part in number.";
break;
case rapidjson::ParseErrorCode::kParseErrorNumberMissExponent:
name = "Miss exponent in number.";
break;
case rapidjson::ParseErrorCode::kParseErrorTermination:
name = "Parsing was terminated.";
break;
case rapidjson::ParseErrorCode::kParseErrorUnspecificSyntaxError:
name = "Unspecific syntax error.";
break;
default:
break;
}
return formatter<string_view>::format(name, ctx);
}
};
/** @endcond */

View File

@ -0,0 +1,503 @@
#pragma once
#include "Library.h"
#include <glm/gtc/epsilon.hpp>
namespace CesiumUtility {
/**
* @brief Mathematical constants and functions
*/
class CESIUMUTILITY_API Math final {
public:
/** @brief 0.1 */
static constexpr double Epsilon1 = 1e-1;
/** @brief 0.01 */
static constexpr double Epsilon2 = 1e-2;
/** @brief 0.001 */
static constexpr double Epsilon3 = 1e-3;
/** @brief 0.0001 */
static constexpr double Epsilon4 = 1e-4;
/** @brief 0.00001 */
static constexpr double Epsilon5 = 1e-5;
/** @brief 0.000001 */
static constexpr double Epsilon6 = 1e-6;
/** @brief 0.0000001 */
static constexpr double Epsilon7 = 1e-7;
/** @brief 0.00000001 */
static constexpr double Epsilon8 = 1e-8;
/** @brief 0.000000001 */
static constexpr double Epsilon9 = 1e-9;
/** @brief 0.0000000001 */
static constexpr double Epsilon10 = 1e-10;
/** @brief 0.00000000001 */
static constexpr double Epsilon11 = 1e-11;
/** @brief 0.000000000001 */
static constexpr double Epsilon12 = 1e-12;
/** @brief 0.0000000000001 */
static constexpr double Epsilon13 = 1e-13;
/** @brief 0.00000000000001 */
static constexpr double Epsilon14 = 1e-14;
/** @brief 0.000000000000001 */
static constexpr double Epsilon15 = 1e-15;
/** @brief 0.0000000000000001 */
static constexpr double Epsilon16 = 1e-16;
/** @brief 0.00000000000000001 */
static constexpr double Epsilon17 = 1e-17;
/** @brief 0.000000000000000001 */
static constexpr double Epsilon18 = 1e-18;
/** @brief 0.0000000000000000001 */
static constexpr double Epsilon19 = 1e-19;
/** @brief 0.00000000000000000001 */
static constexpr double Epsilon20 = 1e-20;
/** @brief 0.000000000000000000001 */
static constexpr double Epsilon21 = 1e-21;
/**
* @brief pi
*/
static constexpr double OnePi = 3.14159265358979323846;
/**
* @brief two times pi
*/
static constexpr double TwoPi = OnePi * 2.0;
/**
* @brief pi divded by two
*/
static constexpr double PiOverTwo = OnePi / 2.0;
/**
* @brief Converts a relative to an absolute epsilon, for the epsilon-equality
* check between two values.
*
* @tparam L The length type.
* @tparam T value value type.
* @tparam Q The GLM qualifier type.
*
* @param a The first value.
* @param b The second value.
* @param relativeEpsilon The relative epsilon.
* @return The absolute epsilon.
*/
template <glm::length_t L, typename T, glm::qualifier Q>
static constexpr glm::vec<L, T, Q> relativeEpsilonToAbsolute(
const glm::vec<L, T, Q>& a,
const glm::vec<L, T, Q>& b,
double relativeEpsilon) noexcept {
return relativeEpsilon * glm::max(glm::abs(a), glm::abs(b));
}
/**
* @brief Converts a relative to an absolute epsilon, for the epsilon-equality
* check between two values.
*
* @param a The first value.
* @param b The second value.
* @param relativeEpsilon The relative epsilon.
* @return The absolute epsilon.
*/
static constexpr double relativeEpsilonToAbsolute(
double a,
double b,
double relativeEpsilon) noexcept {
return relativeEpsilon * glm::max(glm::abs(a), glm::abs(b));
}
/**
* @brief Checks whether two values are equal up to a given relative epsilon.
*
* @tparam L The length type.
* @tparam T value value type.
* @tparam Q The GLM qualifier type.
*
* @param left The first value.
* @param right The second value.
* @param relativeEpsilon The relative epsilon.
* @return Whether the values are epsilon-equal
*/
template <glm::length_t L, typename T, glm::qualifier Q>
static bool constexpr equalsEpsilon(
const glm::vec<L, T, Q>& left,
const glm::vec<L, T, Q>& right,
double relativeEpsilon) noexcept {
return Math::equalsEpsilon(left, right, relativeEpsilon, relativeEpsilon);
}
/**
* @brief Checks whether two values are equal up to a given relative epsilon.
*
* @param left The first value.
* @param right The second value.
* @param relativeEpsilon The relative epsilon.
* @return Whether the values are epsilon-equal
*/
static constexpr bool
equalsEpsilon(double left, double right, double relativeEpsilon) noexcept {
return equalsEpsilon(left, right, relativeEpsilon, relativeEpsilon);
}
/**
* @brief Determines if two values are equal using an absolute or relative
* tolerance test.
*
* This is useful to avoid problems due to roundoff error when comparing
* floating-point values directly. The values are first compared using an
* absolute tolerance test. If that fails, a relative tolerance test is
* performed. Use this test if you are unsure of the magnitudes of left and
* right.
*
* @param left The first value to compare.
* @param right The other value to compare.
* @param relativeEpsilon The maximum inclusive delta between `left` and
* `right` for the relative tolerance test.
* @param absoluteEpsilon The maximum inclusive delta between `left` and
* `right` for the absolute tolerance test.
* @returns `true` if the values are equal within the epsilon; otherwise,
* `false`.
*
* @snippet TestMath.cpp equalsEpsilon
*/
static constexpr bool equalsEpsilon(
double left,
double right,
double relativeEpsilon,
double absoluteEpsilon) noexcept {
const double diff = glm::abs(left - right);
return diff <= absoluteEpsilon ||
diff <= relativeEpsilonToAbsolute(left, right, relativeEpsilon);
}
/**
* @brief Determines if two values are equal using an absolute or relative
* tolerance test.
*
* This is useful to avoid problems due to roundoff error when comparing
* floating-point values directly. The values are first compared using an
* absolute tolerance test. If that fails, a relative tolerance test is
* performed. Use this test if you are unsure of the magnitudes of left and
* right.
*
* @tparam L The length type.
* @tparam T value value type.
* @tparam Q The GLM qualifier type.
*
* @param left The first value to compare.
* @param right The other value to compare.
* @param relativeEpsilon The maximum inclusive delta between `left` and
* `right` for the relative tolerance test.
* @param absoluteEpsilon The maximum inclusive delta between `left` and
* `right` for the absolute tolerance test.
* @returns `true` if the values are equal within the epsilon; otherwise,
* `false`.
*/
template <glm::length_t L, typename T, glm::qualifier Q>
static constexpr bool equalsEpsilon(
const glm::vec<L, T, Q>& left,
const glm::vec<L, T, Q>& right,
double relativeEpsilon,
double absoluteEpsilon) noexcept {
const glm::vec<L, T, Q> diff = glm::abs(left - right);
return glm::lessThanEqual(diff, glm::vec<L, T, Q>(absoluteEpsilon)) ==
glm::vec<L, bool, Q>(true) ||
glm::lessThanEqual(
diff,
relativeEpsilonToAbsolute(left, right, relativeEpsilon)) ==
glm::vec<L, bool, Q>(true);
}
/**
* @brief Returns the sign of the value.
*
* This is 1 if the value is positive, -1 if the value is
* negative, or 0 if the value is 0.
*
* @param value The value to return the sign of.
* @returns The sign of value.
*/
static constexpr double sign(double value) noexcept {
if (value == 0.0 || value != value) {
// zero or NaN
return value;
}
return value > 0 ? 1 : -1;
}
/**
* @brief Returns 1.0 if the given value is positive or zero, and -1.0 if it
* is negative.
*
* This is similar to {@link Math::sign} except that returns 1.0 instead of
* 0.0 when the input value is 0.0.
*
* @param value The value to return the sign of.
* @returns The sign of value.
*/
static constexpr double signNotZero(double value) noexcept {
return value < 0.0 ? -1.0 : 1.0;
}
/**
* @brief Produces an angle in the range -Pi <= angle <= Pi which is
* equivalent to the provided angle.
*
* @param angle The angle in radians.
* @returns The angle in the range [`-Math::OnePi`, `Math::OnePi`].
*/
static double negativePiToPi(double angle) noexcept {
if (angle >= -Math::OnePi && angle <= Math::OnePi) {
// Early exit if the input is already inside the range. This avoids
// unnecessary math which could introduce floating point error.
return angle;
}
return Math::zeroToTwoPi(angle + Math::OnePi) - Math::OnePi;
}
/**
* @brief Produces an angle in the range 0 <= angle <= 2Pi which is equivalent
* to the provided angle.
*
* @param angle The angle in radians.
* @returns The angle in the range [0, `Math::TwoPi`].
*/
static double zeroToTwoPi(double angle) noexcept {
if (angle >= 0 && angle <= Math::TwoPi) {
// Early exit if the input is already inside the range. This avoids
// unnecessary math which could introduce floating point error.
return angle;
}
const double mod = Math::mod(angle, Math::TwoPi);
if (glm::abs(mod) < Math::Epsilon14 && glm::abs(angle) > Math::Epsilon14) {
return Math::TwoPi;
}
return mod;
}
/**
* @brief The modulo operation that also works for negative dividends.
*
* @param m The dividend.
* @param n The divisor.
* @returns The remainder.
*/
static double mod(double m, double n) noexcept {
if (Math::sign(m) == Math::sign(n) && glm::abs(m) < glm::abs(n)) {
// Early exit if the input does not need to be modded. This avoids
// unnecessary math which could introduce floating point error.
return m;
}
return fmod(fmod(m, n) + n, n);
}
/**
* @brief Converts degrees to radians.
*
* @param angleDegrees The angle to convert in degrees.
* @returns The corresponding angle in radians.
*/
static constexpr double degreesToRadians(double angleDegrees) noexcept {
return angleDegrees * Math::OnePi / 180.0;
}
/**
* @brief Converts radians to degrees.
*
* @param angleRadians The angle to convert in radians.
* @returns The corresponding angle in degrees.
*/
static constexpr double radiansToDegrees(double angleRadians) noexcept {
return angleRadians * 180.0 / Math::OnePi;
}
/**
* @brief Computes the linear interpolation of two values.
*
* @param p The start value to interpolate.
* @param q The end value to interpolate.
* @param time The time of interpolation generally in the range `[0.0, 1.0]`.
* @returns The linearly interpolated value.
*
* @snippet TestMath.cpp lerp
*/
static double lerp(double p, double q, double time) noexcept {
return glm::mix(p, q, time);
}
/**
* @brief Constrain a value to lie between two values.
*
* @param value The value to constrain.
* @param min The minimum value.
* @param max The maximum value.
* @returns The value clamped so that min <= value <= max.
*/
static constexpr double clamp(double value, double min, double max) noexcept {
return glm::clamp(value, min, max);
};
/**
* @brief Converts a scalar value in the range [-1.0, 1.0] to a SNORM in the
* range [0, rangeMaximum]
*
* @param value The scalar value in the range [-1.0, 1.0].
* @param rangeMaximum The maximum value in the mapped range, 255 by default.
* @returns A SNORM value, where 0 maps to -1.0 and rangeMaximum maps to 1.0.
*
* @see Math::fromSNorm
*/
static double toSNorm(double value, double rangeMaximum = 255.0) noexcept {
return glm::round(
(Math::clamp(value, -1.0, 1.0) * 0.5 + 0.5) * rangeMaximum);
};
/**
* @brief Converts a SNORM value in the range [0, rangeMaximum] to a scalar in
* the range [-1.0, 1.0].
*
* @param value SNORM value in the range [0, rangeMaximum].
* @param rangeMaximum The maximum value in the SNORM range, 255 by default.
* @returns Scalar in the range [-1.0, 1.0].
*
* @see Math::toSNorm
*/
static constexpr double
fromSNorm(double value, double rangeMaximum = 255.0) noexcept {
return (Math::clamp(value, 0.0, rangeMaximum) / rangeMaximum) * 2.0 - 1.0;
}
/**
* Converts a longitude value, in radians, to the range [`-Math::OnePi`,
* `Math::OnePi`).
*
* @param angle The longitude value, in radians, to convert to the range
* [`-Math::OnePi`, `Math::OnePi`).
* @returns The equivalent longitude value in the range [`-Math::OnePi`,
* `Math::OnePi`).
*
* @snippet TestMath.cpp convertLongitudeRange
*/
static double convertLongitudeRange(double angle) noexcept {
constexpr double twoPi = Math::TwoPi;
const double simplified = angle - glm::floor(angle / twoPi) * twoPi;
if (simplified < -Math::OnePi) {
return simplified + twoPi;
}
if (simplified >= Math::OnePi) {
return simplified - twoPi;
}
return simplified;
};
/**
* @brief Rounds a value up to the nearest integer, like `ceil`, except
* that if the value is very close to the lower integer it is rounded down
* (like `floor`) instead.
*
* @param value The value to round.
* @param tolerance The tolerance. If the value is closer than this to the
* lower integer, it is rounded down instead.
* @return The rounded value.
*/
static double roundUp(double value, double tolerance) noexcept {
const double up = glm::ceil(value);
const double down = glm::floor(value);
if (value - down < tolerance) {
return down;
} else {
return up;
}
}
/**
* @brief Rounds a value down to the nearest integer, like `floor`, except
* that if the value is very close to the higher integer it is rounded up
* (like `ceil`) instead.
*
* @param value The value to round.
* @param tolerance The tolerance. If the value is closer than this to the
* higher integer, it is rounded up instead.
* @return The rounded value.
*/
static double roundDown(double value, double tolerance) noexcept {
const double up = glm::ceil(value);
const double down = glm::floor(value);
if (up - value < tolerance) {
return up;
} else {
return down;
}
}
/**
* @brief Construct a vector perpendicular to the argument.
* @param v The input vector
* @return A vector perpendicular to the input vector
*/
template <typename T, glm::qualifier Q>
static glm::vec<3, T, Q> perpVec(const glm::vec<3, T, Q>& v) {
// This constructs a vector whose dot product with v will be 0, hence
// perpendicular to v. As seen in the "Physically Based Rendering".
if (std::abs(v.x) > std::abs(v.y)) {
return glm::vec<3, T, Q>(-v.z, 0, v.x) / std::sqrt(v.x * v.x + v.z * v.z);
}
return glm::vec<3, T, Q>(0, v.z, -v.y) / std::sqrt(v.y * v.y + v.z * v.z);
}
/** @brief Compute the rotation between two unit vectors.
* @param vec1 The first vector.
* @param vec2 The second vector.
* @return A quaternion representing the rotation of vec1 to vec2.
*/
template <typename T, glm::qualifier Q>
static glm::qua<T, Q>
rotation(const glm::vec<3, T, Q>& vec1, const glm::vec<3, T, Q>& vec2) {
// If we take the dot and cross products of the two vectors and store
// them in a quaternion, that quaternion represents twice the required
// rotation. We get the correct quaternion by "averaging" with the zero
// rotation quaternion, in a way analagous to finding the half vector
// between two 3D vectors.
auto cosRot = dot(vec1, vec2);
auto rotAxis = cross(vec1, vec2);
auto rotAxisLen2 = dot(rotAxis, rotAxis);
// Not using epsilon for these tests. If abs(cosRot) < 1.0, we can still
// create a sensible rotation.
if (cosRot >= 1 || (rotAxisLen2 == 0 && cosRot > 0)) {
// zero rotation
return glm::qua<T, Q>(1, 0, 0, 0);
}
if (cosRot <= -1 || (rotAxisLen2 == 0 && cosRot < 0)) {
auto perpAxis = CesiumUtility::Math::perpVec(vec1);
// rotation by pi radians
return glm::qua<T, Q>(0, perpAxis);
}
glm::qua<T, Q> sumQuat(cosRot + 1, rotAxis);
return normalize(sumQuat);
}
};
} // namespace CesiumUtility

View File

@ -0,0 +1,136 @@
#pragma once
#include <CesiumUtility/Assert.h>
#include <atomic>
#include <cstdint>
#ifndef NDEBUG
#include <thread>
#endif
namespace CesiumUtility {
/** \cond Doxygen_Suppress */
#ifndef NDEBUG
template <bool isThreadSafe> class ThreadIdHolder;
template <> class ThreadIdHolder<false> {
ThreadIdHolder() : _threadID(std::this_thread::get_id()) {}
std::thread::id _threadID;
template <typename T, bool isThreadSafe> friend class ReferenceCounted;
};
template <> class ThreadIdHolder<true> {};
#endif
/** \endcond */
/**
* @brief A reference-counted base class, meant to be used with
* {@link IntrusivePointer}.
*
* Consider using {@link ReferenceCountedThreadSafe} or
* {@link ReferenceCountedNonThreadSafe} instead of using this class directly.
*
* @tparam T The type that is _deriving_ from this class. For example, you
* should declare your class as
* `class MyClass : public ReferenceCounted<MyClass> { ... };`
* @tparam isThreadSafe If `true`, the reference count will be thread-safe by
* using `std::atomic`, allowing references to safely be added and removed from
* any thread at any time. The object will be destroyed in the thread that
* releases the last reference. If false, it uses a simple integer for the
* reference count, which is not thread safe. In this case, references must be
* added and removed (including automatically via `IntrusivePointer`) from only
* one thread at a time. However, this mode has a bit less overhead for objects
* that are only ever accessed from a single thread.
*/
template <typename T, bool isThreadSafe = true>
class ReferenceCounted
#ifndef NDEBUG
: public ThreadIdHolder<isThreadSafe>
#endif
{
public:
ReferenceCounted() noexcept {}
~ReferenceCounted() noexcept { CESIUM_ASSERT(this->_referenceCount == 0); }
/**
* @brief Adds a counted reference to this object. Use
* {@link CesiumUtility::IntrusivePointer} instead of calling this method
* directly.
*/
void addReference() const /*noexcept*/ {
#ifndef NDEBUG
if constexpr (!isThreadSafe) {
CESIUM_ASSERT(std::this_thread::get_id() == this->_threadID);
}
#endif
++this->_referenceCount;
}
/**
* @brief Removes a counted reference from this object. When the last
* reference is removed, this method will delete this instance. Use
* {@link CesiumUtility::IntrusivePointer} instead of calling this method
* directly.
*/
void releaseReference() const /*noexcept*/ {
#ifndef NDEBUG
if constexpr (!isThreadSafe) {
CESIUM_ASSERT(std::this_thread::get_id() == this->_threadID);
}
#endif
CESIUM_ASSERT(this->_referenceCount > 0);
const int32_t references = --this->_referenceCount;
if (references == 0) {
delete static_cast<const T*>(this);
}
}
/**
* @brief Returns the current reference count of this instance.
*/
std::int32_t getReferenceCount() const noexcept {
return this->_referenceCount;
}
private:
using ThreadSafeCounter = std::atomic<std::int32_t>;
using NonThreadSafeCounter = std::int32_t;
using CounterType =
std::conditional_t<isThreadSafe, ThreadSafeCounter, NonThreadSafeCounter>;
mutable CounterType _referenceCount{0};
};
/**
* @brief A reference-counted base class, meant to be used with
* {@link IntrusivePointer}. The reference count is thread-safe, so references
* may be added and removed from any thread at any time. The object will be
* destroyed in the thread that releases the last reference.
*
* @tparam T The type that is _deriving_ from this class. For example, you
* should declare your class as
* `class MyClass : public ReferenceCountedThreadSafe<MyClass> { ... };`
*/
template <typename T>
using ReferenceCountedThreadSafe = ReferenceCounted<T, true>;
/**
* @brief A reference-counted base class, meant to be used with
* {@link IntrusivePointer}. The reference count is not thread-safe, so
* references must be added and removed (including automatically via
* `IntrusivePointer`) from only one thread at a time.
*
* @tparam T The type that is _deriving_ from this class. For example, you
* should declare your class as
* `class MyClass : public ReferenceCountedNonThreadSafe<MyClass> { ... };`
*/
template <typename T>
using ReferenceCountedNonThreadSafe = ReferenceCounted<T, false>;
} // namespace CesiumUtility

View File

@ -0,0 +1,124 @@
#pragma once
#include <CesiumUtility/ErrorList.h>
#include <CesiumUtility/IntrusivePointer.h>
#include <optional>
namespace CesiumUtility {
/**
* @brief Holds the result of an operation. If the operation succeeds, it will
* provide a value. It may also provide errors and warnings.
*
* @tparam T The type of value included in the result.
*/
template <typename T> struct Result {
/**
* @brief Creates a `Result` with the given value and an empty \ref ErrorList.
*
* @param value_ The value that will be contained in this `Result`.
*/
Result(T value_) noexcept : value(std::move(value_)), errors() {}
/**
* @brief Creates a `Result` with the given value and \ref ErrorList.
*
* @param value_ The value that will be contained in this `Result`.
* @param errors_ The \ref ErrorList containing errors and warnings related to
* this `Result`.
*/
Result(T value_, ErrorList errors_) noexcept
: value(std::move(value_)), errors(std::move(errors_)) {}
/**
* @brief Creates a `Result` with an empty value and the given \ref ErrorList.
*
* @param errors_ The \ref ErrorList containing errors and warnings related to
* this `Result`.
*/
Result(ErrorList errors_) noexcept : value(), errors(std::move(errors_)) {}
/**
* @brief The value, if the operation succeeded to the point where it can
* provide one.
*
* If a value is not provided because the operation failed, then there should
* be at least one error in {@link errors} indicating what went wrong.
*/
std::optional<T> value;
/**
* @brief The errors and warnings that occurred during the operation.
*
* If a {@link value} is provided, there should not be any errors in this
* list, but there may be warnings. If a value is not provided, there should
* be at least one error in this list.
*/
ErrorList errors;
};
/**
* @brief Holds the result of an operation. If the operation succeeds, it will
* provide a value. It may also provide errors and warnings.
*
* @tparam T The type of value included in the result.
*/
template <typename T> struct Result<CesiumUtility::IntrusivePointer<T>> {
/**
* @brief Creates a `Result` with the given value and an empty \ref ErrorList.
*
* @param pValue_ An intrusive pointer to the value that will be contained in
* this `Result`.
*/
Result(CesiumUtility::IntrusivePointer<T> pValue_) noexcept
: pValue(std::move(pValue_)), errors() {}
/**
* @brief Creates a `Result` with the given value and \ref ErrorList.
*
* @param pValue_ An intrusive pointer to the value that will be contained in
* this `Result`.
* @param errors_ The \ref ErrorList containing errors and warnings related to
* this `Result`.
*/
Result(CesiumUtility::IntrusivePointer<T> pValue_, ErrorList errors_) noexcept
: pValue(std::move(pValue_)), errors(std::move(errors_)) {}
/**
* @brief Creates a `Result` with an empty value and the given \ref ErrorList.
*
* @param errors_ The \ref ErrorList containing errors and warnings related to
* this `Result`.
*/
Result(ErrorList errors_) noexcept
: pValue(nullptr), errors(std::move(errors_)) {}
/**
* @brief The value, if the operation succeeded to the point where it can
* provide one.
*
* If a value is not provided because the operation failed, then there should
* be at least one error in {@link errors} indicating what went wrong.
*/
CesiumUtility::IntrusivePointer<T> pValue;
/**
* @brief The errors and warnings that occurred during the operation.
*
* If a {@link pValue} is provided, there should not be any errors in this
* list, but there may be warnings. If a pValue is not provided, there should
* be at least one error in this list.
*/
ErrorList errors;
};
/**
* @brief A convenient shortcut for
* `CesiumUtility::Result<CesiumUtility::IntrusivePointer<T>>`.
*
* @tparam T The type of object that the IntrusivePointer points to.
*/
template <typename T> using ResultPointer = Result<IntrusivePointer<T>>;
} // namespace CesiumUtility

View File

@ -0,0 +1,87 @@
#pragma once
#include <type_traits>
#include <utility>
namespace CesiumUtility {
/**
* @brief A utility that will automatically call the lambda function when
* exiting a scope.
*
* @tparam ExitFunction The function type to be called when the guard is out of
* scope.
*/
template <typename ExitFunction> class ScopeGuard {
public:
/**
* @brief Constructor.
*
* @param exitFunc The function type to be called when the guard is out
* of scope
*/
template <
typename ExitFunctionArg,
typename std::enable_if_t<
!std::is_same_v<
std::remove_reference_t<std::remove_const_t<ExitFunctionArg>>,
ScopeGuard<ExitFunction>>,
int> = 0>
explicit ScopeGuard(ExitFunctionArg&& exitFunc)
: _callExitFuncOnDestruct{true},
_exitFunc{std::forward<ExitFunctionArg>(exitFunc)} {}
ScopeGuard(const ScopeGuard& rhs) = delete;
/**
* @brief Move constructor. The rhs will move its lambda to the lhs, and the
* rhs will not call its lambda upon exiting a scope
*/
ScopeGuard(ScopeGuard&& rhs) noexcept
: _callExitFuncOnDestruct{rhs._callExitFuncOnDestruct},
_exitFunc{std::move(rhs._exitFunc)} {
rhs.release();
}
ScopeGuard& operator=(const ScopeGuard& rhs) = delete;
/**
* @brief Move assignment operator. The rhs will move its lambda to the lhs,
* and the rhs will not call its lambda upon exiting a scope
*/
ScopeGuard& operator=(ScopeGuard&& rhs) noexcept {
if (&rhs != this) {
_callExitFuncOnDestruct = rhs._callExitFuncOnDestruct;
_exitFunc = std::move(rhs._exitFunc);
rhs.release();
}
return *this;
}
/**
* @brief Destructor. The guard will execute the lambda function when exiting
* a scope if it's not released
*/
~ScopeGuard() noexcept {
if (_callExitFuncOnDestruct) {
_exitFunc();
}
}
/**
* @brief Upon calling ScopeGuard::release(), the guard will not execute the
* lambda function when exiting a scope
*/
void release() noexcept { _callExitFuncOnDestruct = false; }
private:
bool _callExitFuncOnDestruct;
ExitFunction _exitFunc;
};
/** @brief Template deduction guide for \ref ScopeGuard to help the compiler
* figure out that the type of `ExitFunction` should be the type of the
* function passed to the constructor. */
template <typename ExitFunction>
ScopeGuard(ExitFunction) -> ScopeGuard<ExitFunction>;
} // namespace CesiumUtility

View File

@ -0,0 +1,154 @@
#pragma once
#include <CesiumUtility/Assert.h>
#include <CesiumUtility/ExtensibleObject.h>
#include <CesiumUtility/IDepotOwningAsset.h>
#include <CesiumUtility/Library.h>
#include <atomic>
namespace CesiumAsync {
template <typename TAssetType, typename TAssetKey> class SharedAssetDepot;
}
namespace CesiumUtility {
/**
* @brief An asset that is potentially shared between multiple objects, such as
* an image shared between multiple glTF models. This is intended to be the base
* class for such assets.
*
* The lifetime of instances of this class should be managed by reference
* counting with {@link IntrusivePointer}.
*
* @tparam T The type that is _deriving_ from this class. For example, you
* should declare your class as
* `class MyClass : public SharedAsset<MyClass> { ... };`
*
* @remarks @parblock
* A `SharedAsset` can be in one of three possible states:
*
* **Independent Asset**
* An independent asset isn't affiliated with an asset depot at all.
* Its lifetime is controlled exclusively by IntrusivePointer / reference
* counting. When the asset's reference count goes to zero, it deletes itself.
* An independent asset's {@link getDepot} returns nullptr.
*
* **Active Depot Asset**
* This is an asset that is owned by an asset depot and that is in use, meaning
* it has a reference count greater than zero. The asset depot owns the asset
* via an `std::unique_ptr`, not via adding to the reference count. So when the
* reference count goes to zero, only the asset depot itself still has a
* reference to it, so it becomes an inactive depot asset.
*
* **Inactive Depot Asset**
* This is also an asset that is owned by the asset depot, but there are no
* other references to it (it has a reference count of zero). It is found in the
* asset depot's `deletionCandidates` list. When a reference to it is added, it
* is removed from `deletionCandidates` and it becomes an active depot asset.
* @endparblock
*/
template <typename T>
class CESIUMUTILITY_API SharedAsset : public CesiumUtility::ExtensibleObject {
public:
/**
* @brief Adds a counted reference to this object. Use
* {@link CesiumUtility::IntrusivePointer} instead of calling this method
* directly.
*/
void addReference() const noexcept { this->addReference(false); }
/**
* @brief Removes a counted reference from this object. When the last
* reference is removed, this method will delete this instance. Use
* {@link CesiumUtility::IntrusivePointer} instead of calling this method
* directly.
*/
void releaseReference() const noexcept { this->releaseReference(false); }
/**
* @brief Gets the shared asset depot that owns this asset, or nullptr if this
* asset is independent of an asset depot.
*/
const IDepotOwningAsset<T>* getDepot() const { return this->_pDepot; }
/**
* @brief Gets the shared asset depot that owns this asset, or nullptr if this
* asset is independent of an asset depot.
*/
IDepotOwningAsset<T>* getDepot() { return this->_pDepot; }
protected:
SharedAsset() = default;
~SharedAsset() { CESIUM_ASSERT(this->_referenceCount == 0); }
/**
* Assets can be copied, but the fresh instance has no references and is not
* in the asset depot.
*/
SharedAsset(const SharedAsset& rhs)
: ExtensibleObject(rhs), _referenceCount(0), _pDepot(nullptr) {}
/**
* After a move construction, the content of the asset is moved to the new
* instance, but the asset depot still references the old instance.
*/
SharedAsset(SharedAsset&& rhs)
: ExtensibleObject(std::move(rhs)),
_referenceCount(0),
_pDepot(nullptr) {}
/**
* Assignment does not affect the asset's relationship with the depot, but is
* useful to assign the data in derived classes.
*/
SharedAsset& operator=(const SharedAsset& rhs) {
CesiumUtility::ExtensibleObject::operator=(rhs);
return *this;
}
/**
* Assignment does not affect the asset's relationship with the depot, but is
* useful to assign the data in derived classes.
*/
SharedAsset& operator=(SharedAsset&& rhs) {
CesiumUtility::ExtensibleObject::operator=(std::move(rhs));
return *this;
}
private:
void addReference(bool threadOwnsDepotLock) const noexcept {
const int32_t prevReferences = this->_referenceCount++;
if (this->_pDepot && prevReferences <= 0) {
this->_pDepot->unmarkDeletionCandidate(
*static_cast<const T*>(this),
threadOwnsDepotLock);
}
}
void releaseReference(bool threadOwnsDepotLock) const noexcept {
CESIUM_ASSERT(this->_referenceCount > 0);
const int32_t references = --this->_referenceCount;
if (references == 0) {
IDepotOwningAsset<T>* pDepot = this->_pDepot;
if (pDepot) {
// Let the depot manage this object's lifetime.
pDepot->markDeletionCandidate(
*static_cast<const T*>(this),
threadOwnsDepotLock);
} else {
// No depot, so destroy this object directly.
delete static_cast<const T*>(this);
}
}
}
mutable std::atomic<std::int32_t> _referenceCount{0};
IDepotOwningAsset<T>* _pDepot{nullptr};
// To allow the depot to modify _pDepot.
template <typename TAssetType, typename TAssetKey>
friend class CesiumAsync::SharedAssetDepot;
};
} // namespace CesiumUtility

View File

@ -0,0 +1,18 @@
#pragma once
#include <span>
namespace CesiumUtility {
/**
* @brief This function converts between span types. This function
* has the same rules with C++ reintepret_cast
* https://en.cppreference.com/w/cpp/language/reinterpret_cast. So please use it
* carefully
*/
template <typename To, typename From>
std::span<To> reintepretCastSpan(const std::span<From>& from) noexcept {
return std::span<To>(
reinterpret_cast<To*>(from.data()),
from.size() * sizeof(From) / sizeof(To));
}
} // namespace CesiumUtility

View File

@ -0,0 +1,22 @@
#pragma once
#include <string>
namespace CesiumUtility {
/**
* @brief Helper functions for working with strings.
*/
class StringHelpers {
public:
/**
* @brief Converts a `u8string` to a `string` without changing its encoding.
* The output string is encoded in UTF-8, just like the input.
*
* @param s The `std::u8string`.
* @return The equivalent `std::string`.
*/
static std::string toStringUtf8(const std::u8string& s);
};
} // namespace CesiumUtility

View File

@ -0,0 +1,349 @@
#pragma once
#ifndef CESIUM_OVERRIDE_TRACING
// If the build system doesn't enable the tracing support
// consider it disabled by default.
#ifndef CESIUM_TRACING_ENABLED
#define CESIUM_TRACING_ENABLED 0
#endif
#if !CESIUM_TRACING_ENABLED
#define CESIUM_TRACE_INIT(filename)
#define CESIUM_TRACE_SHUTDOWN()
#define CESIUM_TRACE(name)
#define CESIUM_TRACE_BEGIN(name)
#define CESIUM_TRACE_END(name)
#define CESIUM_TRACE_BEGIN_IN_TRACK(name)
#define CESIUM_TRACE_END_IN_TRACK(name)
#define CESIUM_TRACE_DECLARE_TRACK_SET(id, name)
#define CESIUM_TRACE_USE_TRACK_SET(id)
#define CESIUM_TRACE_LAMBDA_CAPTURE_TRACK() tracingTrack = false
#define CESIUM_TRACE_USE_CAPTURED_TRACK()
#else
#include <atomic>
#include <chrono>
#include <fstream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
// helper macros to avoid shadowing variables
#define TRACE_NAME_AUX1(A, B) A##B
#define TRACE_NAME_AUX2(A, B) TRACE_NAME_AUX1(A, B)
/**
* @brief Initializes the tracing framework and begins recording to a given JSON
* filename.
*
* @param filename The path and named of the file in which to record traces.
*/
#define CESIUM_TRACE_INIT(filename) \
CesiumUtility::CesiumImpl::Tracer::instance().startTracing(filename)
/**
* @brief Shuts down tracing and closes the JSON tracing file.
*/
#define CESIUM_TRACE_SHUTDOWN() \
CesiumUtility::CesiumImpl::Tracer::instance().endTracing()
/**
* @brief Measures and records the time spent in the current scope.
*
* The time is measured from the `CESIUM_TRACE` line to the end of the scope.
* If the current thread is enlisted in an async process
* ({@link CESIUM_TRACE_ASYNC_ENLIST}), the time is recorded against the async
* process. Otherwise, it is recorded against the current thread.
*
* @param name The name of the measured operation.
*/
#define CESIUM_TRACE(name) \
CesiumUtility::CesiumImpl::ScopedTrace TRACE_NAME_AUX2( \
cesiumTrace, \
__LINE__)(name)
/**
* @brief Begins measuring an operation which may span scope but not threads.
*
* Begins measuring the time of an operation which completes with a
* corresponding call to {@link CESIUM_TRACE_END}. If the calling thread is
* operating in a track ({@link CESIUM_TRACE_USE_TRACK_SET}), the
* time is recorded in the track. Otherwise, is is recorded against the current
* thread.
*
* Extreme care must be taken to match calls to `CESIUM_TRACE_BEGIN` and
* `CESIUM_TRACE_END`:
*
* * Paired calls must use an identical `name`.
* * If either BEGIN or END is called from a thread operating in a track,
* then both must be, and it must be the same track in
* both cases. In this case BEGIN and END may be called from different
* threads. However, it safer to use {@link CESIUM_TRACE_BEGIN_IN_TRACK}
* in this scenario.
* * If either BEGIN or END is called from a thread _not_ enlisted into a
* track, then both must be called from the same thread and neither
* thread may be in a track.
* * Paired calls must not be interleaved with other BEGIN/END calls for the
* same thread or track. Other BEGIN/END pairs may be fully
* nested within this one, but this pair must not END in between a nested
* measurement's BEGIN and END calls.
*
* Failure to ensure the above may lead to generation of a trace file that the
* Chromium trace viewer is not able to correctly interpret.
*
* @param name The name of the measured operation.
*/
#define CESIUM_TRACE_BEGIN(name) \
CesiumUtility::CesiumImpl::Tracer::instance().writeAsyncEventBegin(name)
/**
* @brief Ends measuring an operation which may span scopes but not threads.
*
* Finishes measuring the time of an operation that began with a call to
* {@link CESIUM_TRACE_BEGIN}. See the documentation for that macro for more
* details and caveats.
*
* @param name The name of the measured operation.
*/
#define CESIUM_TRACE_END(name) \
CesiumUtility::CesiumImpl::Tracer::instance().writeAsyncEventEnd(name)
/**
* @brief Begins measuring an operation that may span both scopes and threads.
*
* This macro is identical to {@link CESIUM_TRACE_BEGIN} except that it does
* nothing if the calling thread and scope are not operating as part of a
* track. This allows it to be safely used to measure operations that span
* threads. Use {@link CESIUM_TRACE_USE_TRACK_SET} to use a track from a set.
*
* @param name The name of the measured operation.
*/
#define CESIUM_TRACE_BEGIN_IN_TRACK(name) \
if (CesiumUtility::CesiumImpl::TrackReference::current() != nullptr) { \
CESIUM_TRACE_BEGIN(name); \
}
/**
* @brief Ends measuring an operation that may span both scopes and threads.
*
* This macro is identical to {@link CESIUM_TRACE_END} except that it does
* nothing if the calling thread and scope are not operating as part of a
* track. This allows it to be safely used to measure operations that span
* threads. Use {@link CESIUM_TRACE_USE_TRACK_SET} to use a track from a set.
*
* @param name The name of the measured operation.
*/
#define CESIUM_TRACE_END_IN_TRACK(name) \
if (CesiumUtility::CesiumImpl::TrackReference::current() != nullptr) { \
CESIUM_TRACE_END(name); \
}
/**
* @brief Declares a set of tracing tracks as a field inside a class.
*
* A track is a sequential process that may take place across multiple threads.
* An instance of a class may have multiple such tracks running simultaneously.
* For example, a single 3D Tiles tile will load in a particular track, while
* other tiles will be loading in other parallel tracks in the same set.
*
* Note that when the track set is destroyed, an assertion will check that no
* tracks are still in progress.
*
* @param id The name of the field to hold the track set.
* @param name A human-friendly name for this set of tracks.
*/
#define CESIUM_TRACE_DECLARE_TRACK_SET(id, name) \
CesiumUtility::CesiumImpl::TrackSet id { name }
/**
* @brief Begins using a track set in this thread.
*
* The calling thread will be allocated a track from the track set, and will
* continue using it for the remainder of the current scope. In addition, if
* the thread starts an async operation using {@link CesiumAsync::AsyncSystem},
* all continuations of that async operation will use the same track as well.
*
* @param id The ID (field name) of the track set declared with
* {@link CESIUM_TRACE_DECLARE_TRACK_SET}.
*/
#define CESIUM_TRACE_USE_TRACK_SET(id) \
CesiumUtility::CesiumImpl::TrackReference TRACE_NAME_AUX2( \
cesiumTraceEnlistTrack, \
__LINE__)(id);
/**
* @brief Capture the current tracing track for a lambda, so that the lambda may
* use the same track.
*
* This macro should be used in a lambda's capture list to capture the track of
* the current thread so that the lambda (which may execute in a different
* thread) can use the same track by executing
* {@link CESIUM_TRACE_USE_CAPTURED_TRACK}.
*/
#define CESIUM_TRACE_LAMBDA_CAPTURE_TRACK() \
tracingTrack = CesiumUtility::CesiumImpl::LambdaCaptureTrack()
/**
* @brief Uses a captured track for the current thread and the current scope.
*
* This macro should be used as the first line in a lambda that should inherit
* the tracing track of the thread that created it. The lambda's capture list
* must also contain {@link CESIUM_TRACE_USE_CAPTURED_TRACK}.
*/
#define CESIUM_TRACE_USE_CAPTURED_TRACK() \
CESIUM_TRACE_USE_TRACK_SET(tracingTrack)
namespace CesiumUtility {
namespace CesiumImpl {
// The following are internal classes used by the tracing framework, do not use
// directly.
struct Trace {
std::string name;
int64_t start;
int64_t duration;
std::thread::id threadID;
};
class TrackReference;
class Tracer {
public:
static Tracer& instance();
~Tracer();
void startTracing(const std::string& filePath = "trace.json");
void endTracing();
void writeCompleteEvent(const Trace& trace);
void writeAsyncEventBegin(const char* name, int64_t id);
void writeAsyncEventBegin(const char* name);
void writeAsyncEventEnd(const char* name, int64_t id);
void writeAsyncEventEnd(const char* name);
int64_t allocateTrackID();
private:
Tracer();
int64_t getCurrentThreadTrackID() const;
void writeAsyncEvent(
const char* category,
const char* name,
char type,
int64_t id);
std::ofstream _output;
uint32_t _numTraces;
std::mutex _lock;
std::atomic<int64_t> _lastAllocatedID;
};
class ScopedTrace {
public:
explicit ScopedTrace(const std::string& message);
~ScopedTrace();
void reset();
ScopedTrace(const ScopedTrace& rhs) = delete;
ScopedTrace(ScopedTrace&& rhs) = delete;
ScopedTrace& operator=(const ScopedTrace& rhs) = delete;
ScopedTrace& operator=(ScopedTrace&& rhs) = delete;
private:
std::string _name;
std::chrono::steady_clock::time_point _startTime;
std::thread::id _threadId;
bool _reset;
};
class TrackSet {
public:
explicit TrackSet(const char* name);
~TrackSet();
size_t acquireTrack();
void addReference(size_t trackIndex) noexcept;
void releaseReference(size_t trackIndex) noexcept;
int64_t getTracingID(size_t trackIndex) noexcept;
TrackSet(TrackSet&& rhs) noexcept;
TrackSet& operator=(TrackSet&& rhs) noexcept;
private:
struct Track {
Track(int64_t id_, bool inUse_)
: id(id_), referenceCount(0), inUse(inUse_) {}
int64_t id;
int32_t referenceCount;
bool inUse;
};
std::string name;
std::vector<Track> tracks;
std::mutex mutex;
};
class LambdaCaptureTrack {
public:
LambdaCaptureTrack();
LambdaCaptureTrack(LambdaCaptureTrack&& rhs) noexcept;
LambdaCaptureTrack(const LambdaCaptureTrack& rhs) noexcept;
~LambdaCaptureTrack();
LambdaCaptureTrack& operator=(const LambdaCaptureTrack& rhs) noexcept;
LambdaCaptureTrack& operator=(LambdaCaptureTrack&& rhs) noexcept;
private:
TrackSet* pSet;
size_t index;
friend class TrackReference;
};
// An RAII object to reference an async operation track.
// When the last instance associated with a particular track index is destroyed,
// the track is released back to the track set.
class TrackReference {
public:
static TrackReference* current();
TrackReference(TrackSet& set) noexcept;
TrackReference(TrackSet& set, size_t index) noexcept;
TrackReference(const LambdaCaptureTrack& lambdaCapture) noexcept;
~TrackReference() noexcept;
operator bool() const noexcept;
int64_t getTracingID() const noexcept;
TrackReference(const TrackReference& rhs) = delete;
TrackReference(TrackReference&& rhs) = delete;
TrackReference& operator=(const TrackReference& rhs) = delete;
TrackReference& operator=(TrackReference&& rhs) = delete;
private:
void enlistCurrentThread();
void dismissCurrentThread();
TrackSet* pSet;
size_t index;
static thread_local std::vector<TrackReference*> _threadEnlistedTracks;
friend class LambdaCaptureTrack;
};
} // namespace CesiumImpl
} // namespace CesiumUtility
#endif // CESIUM_TRACING_ENABLED
#endif

View File

@ -0,0 +1,211 @@
#pragma once
#include <functional>
#include <string>
namespace CesiumUtility {
/**
* @brief A class for building and manipulating Uniform Resource Identifiers
* (URIs).
*/
class Uri final {
public:
/**
* @brief Attempts to resolve a relative URI using a base URI.
*
* For example, a relative URI `/v1/example` together with the base URI
* `https://api.cesium.com` would resolve to
* `https://api.cesium.com/v1/example`.
*
* @param base The base URI that the relative URI is relative to.
* @param relative The relative URI to be resolved against the base URI.
* @param useBaseQuery If true, any query parameters of the base URI will be
* retained in the resolved URI.
* @param assumeHttpsDefault If true, protocol-relative URIs (such as
* `//api.cesium.com`) will be assumed to be `https`. If false, they will be
* assumed to be `http`.
* @returns The resolved URI.
*/
static std::string resolve(
const std::string& base,
const std::string& relative,
bool useBaseQuery = false,
bool assumeHttpsDefault = true);
/**
* @brief Adds the given key and value to the query string of a URI. For
* example, `addQuery("https://api.cesium.com/v1/example", "key", "value")`
* would produce the URL `https://api.cesium.com/v1/example?key=value`.
*
* @param uri The URI whose query string will be modified.
* @param key The key to be added to the query string.
* @param value The value to be added to the query string.
* @returns The modified URI including the new query string parameter.
*/
static std::string addQuery(
const std::string& uri,
const std::string& key,
const std::string& value);
/**
* @brief Obtains the value of the given key from the query string of the URI,
* if possible.
*
* If the URI can't be parsed, or the key doesn't exist in the
* query string, an empty string will be returned.
*
* @param uri The URI with a query string to obtain a value from.
* @param key The key whose value will be obtained from the URI, if possible.
* @returns The value of the given key in the query string, or an empty string
* if not found.
*/
static std::string
getQueryValue(const std::string& uri, const std::string& key);
/**
* @brief A callback to fill-in a placeholder value in a URL.
*
* @param placeholder The text of the placeholder. For example, if the
* placeholder was `{example}`, the value of `placeholder` will be `example`.
* @returns The value to use in place of the placeholder.
*/
typedef std::string
SubstitutionCallbackSignature(const std::string& placeholder);
/**
* @brief Substitutes the placeholders in a templated URI with their
* appropriate values obtained using a specified callback function.
*
* A templated URI has placeholders in the form of `{name}`. For example,
* `https://example.com/{x}/{y}/{z}` has three placeholders, `x`, `y`, and `z`.
* The callback will be called for each placeholder and they will be replaced
* with the return value of this callback.
*
* @param templateUri The templated URI whose placeholders will be substituted
* by this method.
* @param substitutionCallback The callback that will be called for each
* placeholder in the provided URI. See \ref SubstitutionCallbackSignature.
*/
static std::string substituteTemplateParameters(
const std::string& templateUri,
const std::function<SubstitutionCallbackSignature>& substitutionCallback);
/**
* @brief Escapes a portion of a URI, percent-encoding disallowed characters.
*
* @param s The string to escape.
* @return The escaped string.
*/
static std::string escape(const std::string& s);
/**
* @brief Unescapes a portion of a URI, decoding any percent-encoded
* characters.
*
* @param s The string to unescape.
* @return The unescaped string.
*/
static std::string unescape(const std::string& s);
/**
* @brief Converts a Unix file system path to a string suitable for use as the
* path portion of a URI. Characters that are not allowed in the path portion
* of a URI are percent-encoded as necessary.
*
* If the path is absolute (it starts with a slash), then the URI will start
* with a slash as well.
*
* @param unixPath The file system path.
* @return The URI path.
*/
static std::string unixPathToUriPath(const std::string& unixPath);
/**
* @brief Converts a Windows file system path to a string suitable for use as
* the path portion of a URI. Characters that are not allowed in the path
* portion of a URI are percent-encoded as necessary.
*
* Either `/` or `\` may be used as a path separator on input, but the output
* will contain only `/`.
*
* If the path is absolute (it starts with a slash or with C:\ or similar),
* then the URI will start with a slash well.
*
* @param windowsPath The file system path.
* @return The URI path.
*/
static std::string windowsPathToUriPath(const std::string& windowsPath);
/**
* @brief Converts a file system path on the current system to a string
* suitable for use as the path portion of a URI. Characters that are not
* allowed in the path portion of a URI are percent-encoded as necessary.
*
* If the `_WIN32` preprocessor definition is defined when compiling
* cesium-native, this is assumed to be a Windows-like system and this
* function calls {@link windowsPathToUriPath}. Otherwise, this is assumed to
* be a Unix-like system and this function calls {@link unixPathToUriPath}.
*
* @param nativePath The file system path.
* @return The URI path.
*/
static std::string nativePathToUriPath(const std::string& nativePath);
/**
* @brief Converts the path portion of a URI to a Unix file system path.
* Percent-encoded characters in the URI are decoded.
*
* If the URI path is absolute (it starts with a slash), then the file system
* path will start with a slash as well.
*
* @param uriPath The URI path.
* @return The file system path.
*/
static std::string uriPathToUnixPath(const std::string& uriPath);
/**
* @brief Converts the path portion of a URI to a Windows file system path.
* Percent-encoded characters in the URI are decoded.
*
* If the URI path is absolute (it starts with a slash), then the file system
* path will start with a slash or a drive letter.
*
* @param uriPath The URI path.
* @return The file system path.
*/
static std::string uriPathToWindowsPath(const std::string& uriPath);
/**
* @brief Converts the path portion of a URI to a file system path on the
* current system. Percent-encoded characters in the URI are decoded.
*
* If the `_WIN32` preprocessor definition is defined when compiling
* cesium-native, this is assumed to be a Windows-like system and this
* function calls {@link uriPathToWindowsPath}. Otherwise, this is assumed to
* be a Unix-like system and this function calls {@link uriPathToUnixPath}.
*
* @param uriPath The URI path.
* @return The file system path.
*/
static std::string uriPathToNativePath(const std::string& uriPath);
/**
* @brief Gets the path portion of the URI. This will not include path
* parameters, if present.
*
* @param uri The URI from which to get the path.
* @return The path, or empty string if the URI could not be parsed.
*/
static std::string getPath(const std::string& uri);
/**
* @brief Sets the path portion of a URI to a new value. The other portions of
* the URI are left unmodified, including any path parameters.
*
* @param uri The URI for which to set the path.
* @param newPath The new path portion of the URI.
* @returns The new URI after setting the path. If the original URI cannot be
* parsed, it is returned unmodified.
*/
static std::string
setPath(const std::string& uri, const std::string& newPath);
};
} // namespace CesiumUtility

View File

@ -0,0 +1,47 @@
#pragma once
#include <numeric>
#include <string>
namespace CesiumUtility {
/**
* @brief Joins multiple elements together into a string, separated by a given
* separator.
*
* @tparam TIterator The type of the collection iterator.
* @param begin An iterator referring to the first element to join.
* @param end An iterator referring to one past the last element to join.
* @param separator The string to use to separate successive elements.
* @return The joined string.
*/
template <class TIterator>
std::string
joinToString(TIterator begin, TIterator end, const std::string& separator) {
if (begin == end)
return std::string();
std::string first = *begin;
return std::accumulate(
++begin,
end,
std::move(first),
[&separator](const std::string& acc, const std::string& element) {
return acc + separator + element;
});
}
/**
* @brief Joins multiple elements together into a string, separated by a given
* separator.
*
* @tparam TIterator The type of the collection iterator.
* @param collection The collection of elements to be joined.
* @param separator The string to use to separate successive elements.
* @return The joined string.
*/
template <class TCollection>
std::string joinToString(TCollection collection, const std::string& separator) {
return joinToString(collection.cbegin(), collection.cend(), separator);
}
} // namespace CesiumUtility