diff --git a/src/audio/ogg.cpp b/src/audio/ogg.cpp index 436be650b..6afaf849a 100644 --- a/src/audio/ogg.cpp +++ b/src/audio/ogg.cpp @@ -76,7 +76,7 @@ Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) { } -Ogg::Ogg(std::vector data) : memory_data_(std::move(data)), instance_(nullptr) +Ogg::Ogg(Vector data) : memory_data_(std::move(data)), instance_(nullptr) { _init_with_data(); } @@ -153,8 +153,8 @@ OggComment Ogg::comment() const stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_); return OggComment { - std::string(c_comment.vendor), - std::vector(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; + String(c_comment.vendor), + Vector(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; } std::size_t Ogg::sample_rate() const diff --git a/src/audio/ogg.hpp b/src/audio/ogg.hpp index cbee6405a..ef19a0ce0 100644 --- a/src/audio/ogg.hpp +++ b/src/audio/ogg.hpp @@ -35,19 +35,19 @@ public: struct OggComment { - std::string vendor; - std::vector comments; + String vendor; + Vector comments; }; class Ogg final { - std::vector memory_data_; + Vector memory_data_; stb_vorbis* instance_; public: Ogg() noexcept; - explicit Ogg(std::vector data); + explicit Ogg(Vector data); explicit Ogg(tcb::span data); Ogg(const Ogg&) = delete; @@ -77,7 +77,7 @@ private: template , int> = 0> inline Ogg load_ogg(I& stream) { - std::vector data = srb2::io::read_to_vec(stream); + srb2::Vector data = srb2::io::read_to_vec(stream); return Ogg {std::move(data)}; } diff --git a/src/audio/wav.cpp b/src/audio/wav.cpp index 42d1c1dea..a57eed22b 100644 --- a/src/audio/wav.cpp +++ b/src/audio/wav.cpp @@ -126,9 +126,9 @@ void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header stream.seek(io::SeekFrom::kStart, dest); } -std::vector read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) +Vector read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) { - std::vector samples; + Vector samples; samples.reserve(count); for (std::size_t i = 0; i < count; i++) { @@ -137,9 +137,9 @@ std::vector read_uint8_samples_from_stream(io::SpanStream& stream, std: return samples; } -std::vector read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) +Vector read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) { - std::vector samples; + Vector samples; samples.reserve(count); for (std::size_t i = 0; i < count; i++) { @@ -177,7 +177,7 @@ Wav::Wav(tcb::span data) } std::optional read_fmt; - std::variant, std::vector> interleaved_samples; + std::variant, Vector> interleaved_samples; while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) { @@ -247,7 +247,7 @@ template std::size_t read_samples( std::size_t channels, std::size_t offset, - const std::vector& samples, + const Vector& samples, tcb::span> buffer ) noexcept { @@ -281,8 +281,8 @@ std::size_t read_samples( std::size_t Wav::get_samples(std::size_t offset, tcb::span> buffer) const noexcept { auto samples_visitor = srb2::Overload { - [&](const std::vector& samples) { return read_samples(channels(), offset, samples, buffer); }, - [&](const std::vector& samples) + [&](const Vector& samples) { return read_samples(channels(), offset, samples, buffer); }, + [&](const Vector& samples) { return read_samples(channels(), offset, samples, buffer); }}; return std::visit(samples_visitor, interleaved_samples_); @@ -291,7 +291,7 @@ std::size_t Wav::get_samples(std::size_t offset, tcb::span> buf std::size_t Wav::interleaved_length() const noexcept { auto samples_visitor = srb2::Overload { - [](const std::vector& samples) { return samples.size(); }, - [](const std::vector& samples) { return samples.size(); }}; + [](const Vector& samples) { return samples.size(); }, + [](const Vector& samples) { return samples.size(); }}; return std::visit(samples_visitor, interleaved_samples_); } diff --git a/src/audio/wav.hpp b/src/audio/wav.hpp index 8dff09f3c..fa1aec374 100644 --- a/src/audio/wav.hpp +++ b/src/audio/wav.hpp @@ -15,10 +15,10 @@ #include #include #include -#include #include +#include "../core/vector.hpp" #include "../io/streams.hpp" #include "sample.hpp" @@ -27,7 +27,7 @@ namespace srb2::audio class Wav final { - std::variant, std::vector> interleaved_samples_; + std::variant, Vector> interleaved_samples_; std::size_t channels_ = 1; std::size_t sample_rate_ = 44100; @@ -46,7 +46,7 @@ public: template , int> = 0> inline Wav load_wav(I& stream) { - std::vector data = srb2::io::read_to_vec(stream); + Vector data = srb2::io::read_to_vec(stream); return Wav {data}; } diff --git a/src/audio/xmp.cpp b/src/audio/xmp.cpp index d4e619e3e..9ed496e5f 100644 --- a/src/audio/xmp.cpp +++ b/src/audio/xmp.cpp @@ -50,7 +50,7 @@ Xmp::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(fal } template -Xmp::Xmp(std::vector data) +Xmp::Xmp(Vector data) : data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) { _init(); diff --git a/src/audio/xmp.hpp b/src/audio/xmp.hpp index 7b34ccacd..50aba1711 100644 --- a/src/audio/xmp.hpp +++ b/src/audio/xmp.hpp @@ -15,11 +15,11 @@ #include #include #include -#include #include #include +#include "../core/vector.hpp" #include "../io/streams.hpp" namespace srb2::audio @@ -37,7 +37,7 @@ public: template class Xmp final { - std::vector data_; + Vector data_; xmp_context instance_; bool module_loaded_; bool looping_; @@ -45,7 +45,7 @@ class Xmp final public: Xmp(); - explicit Xmp(std::vector data); + explicit Xmp(Vector data); explicit Xmp(tcb::span data); Xmp(const Xmp&) = delete; @@ -74,7 +74,7 @@ extern template class Xmp<2>; template , int> = 0> inline Xmp load_xmp(I& stream) { - std::vector data = srb2::io::read_to_vec(stream); + Vector data = srb2::io::read_to_vec(stream); return Xmp {std::move(data)}; } diff --git a/src/audio/xmp_player.cpp b/src/audio/xmp_player.cpp index 9062f0a50..35285bacc 100644 --- a/src/audio/xmp_player.cpp +++ b/src/audio/xmp_player.cpp @@ -12,6 +12,8 @@ #include +#include "../core/vector.hpp" + using namespace srb2; using namespace srb2::audio; diff --git a/src/audio/xmp_player.hpp b/src/audio/xmp_player.hpp index e3b31bbfa..bc9e1898c 100644 --- a/src/audio/xmp_player.hpp +++ b/src/audio/xmp_player.hpp @@ -14,6 +14,8 @@ #include "source.hpp" #include "xmp.hpp" +#include "../core/vector.hpp" + namespace srb2::audio { @@ -21,7 +23,7 @@ template class XmpPlayer final : public Source { Xmp xmp_; - std::vector> buf_; + srb2::Vector> buf_; public: XmpPlayer(Xmp&& xmp); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a5d0b521a..5c91e1d66 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,8 +1,17 @@ target_sources(SRB2SDL2 PRIVATE + hash_map.hpp + hash_set.cpp + hash_set.hpp + json.cpp + json.hpp memory.cpp memory.h spmc_queue.hpp static_vec.hpp + string.cpp + string.h thread_pool.cpp thread_pool.h + vector.cpp + vector.hpp ) diff --git a/src/core/hash_map.hpp b/src/core/hash_map.hpp new file mode 100644 index 000000000..8f430aee3 --- /dev/null +++ b/src/core/hash_map.hpp @@ -0,0 +1,678 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_HASH_MAP_HPP +#define SRB2_CORE_HASH_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "../cxxutil.hpp" + +namespace srb2 +{ + +template +class HashSet; + +template +class HashMap +{ + struct Elem; + + using Hasher = std::hash; + using KeyEqual = std::equal_to; + +public: + using Entry = std::pair; + + class ConstIter; + + class Iter + { + const HashMap* self_; + uint32_t bucket_; + Elem* cur_; + + Iter(const HashMap* self, uint32_t bucket, Elem* cur) + : self_(self) + , bucket_(bucket) + , cur_(cur) + {} + + Iter(const ConstIter& r) + : self_(r.iter_.self_) + , bucket_(r.iter_.bucket_) + , cur_(r.iter_.cur_) + {} + + friend class HashMap; + friend class HashSet; + friend class ConstIter; + + public: + Iter() : Iter(nullptr, 0, nullptr) {} + Iter(const Iter&) = default; + Iter(Iter&&) noexcept = default; + ~Iter() = default; + Iter& operator=(const Iter&) = default; + Iter& operator=(Iter&&) noexcept = default; + + Entry& operator*() const noexcept { return cur_->entry; } + Entry* operator->() const noexcept { return &cur_->entry; } + bool operator==(const Iter& r) const noexcept + { + return self_ == r.self_ && bucket_ == r.bucket_ && cur_ == r.cur_; + } + bool operator!=(const Iter& r) const noexcept { return !(*this == r); } + + Iter& operator++() + { + if (cur_ == nullptr) + { + return *this; + } + + if (cur_->next == nullptr) + { + do + { + if (bucket_ < self_->buckets_ - 1) + { + bucket_++; + cur_ = self_->heads_[bucket_]; + } + else + { + self_ = nullptr; + bucket_ = 0; + cur_ = nullptr; + return *this; + } + } while (cur_ == nullptr); + } + else + { + cur_ = cur_->next; + } + + return *this; + } + + Iter operator++(int) + { + Iter itr = *this; + ++(*this); + return itr; + } + }; + + class ConstIter + { + mutable Iter iter_; + + friend class HashMap; + public: + ConstIter() : iter_() {} + ConstIter(const ConstIter&) = default; + ConstIter(ConstIter&&) noexcept = default; + ~ConstIter() = default; + ConstIter& operator=(const ConstIter&) = default; + ConstIter& operator=(ConstIter&&) noexcept = default; + + ConstIter(const Iter& iter) : iter_(iter) {} + + const Entry& operator*() const noexcept { return iter_.cur_->entry; } + const Entry* operator->() const noexcept { return &iter_.cur_->entry; } + bool operator==(const ConstIter& r) const noexcept { return iter_ == r.iter_; } + bool operator!=(const ConstIter& r) const noexcept { return !(*this == r); } + ConstIter& operator++() noexcept + { + ++iter_; + return *this; + } + ConstIter operator++(int) noexcept + { + ConstIter itr = *this; + ++*this; + return itr; + } + }; + + // iter traits + using key_type = K; + using mapped_type = V; + using value_type = Entry; + using size_type = uint32_t; + using difference_type = int64_t; + using hasher = Hasher; + using key_equal = KeyEqual; + using reference = Entry&; + using const_reference = const Entry&; + using pointer = Entry*; + using const_pointer = const Entry*; + using iterator = Iter; + using const_iterator = ConstIter; + +private: + struct Elem + { + Elem* prev; + Elem* next; + Entry entry; + }; + + Elem** heads_ = nullptr; + size_t size_ = 0; + uint32_t buckets_; + Hasher hasher_; + KeyEqual key_equal_; + + constexpr static uint32_t kDefaultBuckets = 16; + + void init_buckets() + { + if (buckets_ == 0) + { + buckets_ = kDefaultBuckets; + } + + std::allocator allocator; + heads_ = allocator.allocate(buckets_); + for (uint32_t i = 0; i < buckets_; i++) + { + heads_[i] = nullptr; + } + } + + void init_if_needed() + { + if (heads_ == nullptr) + { + init_buckets(); + } + } + + void rehash_if_needed(size_t target_size) + { + if (target_size >= buckets_) + { + rehash(std::max(target_size, buckets_ * 2)); + } + } + + + friend class Iter; + friend class ConstIter; + +public: + HashMap() : heads_(nullptr), size_(0), buckets_(0), hasher_(), key_equal_() + { + + } + + HashMap(uint32_t buckets) + : heads_(nullptr) + , size_(0) + , buckets_(buckets) + , hasher_() + , key_equal_() + { + init_buckets(); + } + + HashMap(const HashMap& r) + { + *this = r; + } + + HashMap(HashMap&& r) noexcept + { + *this = std::move(r); + }; + + ~HashMap() + { + clear(); + } + + HashMap(std::initializer_list list) : HashMap(list.size()) + { + for (auto v : list) + { + insert(v); + } + } + + HashMap& operator=(const HashMap& r) + { + clear(); + buckets_ = r.buckets_; + if (buckets_ == 0) + { + return *this; + } + + init_buckets(); + for (auto itr = r.begin(); itr != r.end(); itr++) + { + insert({itr->first, itr->second}); + } + + return *this; + } + + HashMap& operator=(HashMap&& r) noexcept + { + std::swap(buckets_, r.buckets_); + std::swap(size_, r.size_); + std::swap(heads_, r.heads_); + std::swap(hasher_, r.hasher_); + std::swap(key_equal_, r.key_equal_); + return *this; + }; + + constexpr bool empty() const noexcept { return size_ == 0; } + constexpr size_t size() const noexcept { return size_; } + constexpr uint32_t buckets() const noexcept { return buckets_; } + + Iter begin() noexcept + { + if (size_ == 0) + { + return end(); + } + + Iter ret = end(); + for (uint32_t i = 0; i < buckets_; i++) + { + Elem* ptr = heads_[i]; + if (ptr != nullptr) + { + ret = Iter { this, i, ptr }; + break; + } + } + return ret; + } + + ConstIter cbegin() const noexcept + { + ConstIter ret = end(); + for (uint32_t i = 0; i < buckets_; i++) + { + Elem* ptr = heads_[i]; + if (ptr != nullptr) + { + ret = ConstIter { Iter { this, i, ptr } }; + break; + } + } + return ret; + } + + ConstIter begin() const noexcept { return cbegin(); } + + constexpr Iter end() noexcept { return Iter(); } + + constexpr ConstIter cend() const noexcept { return ConstIter(); } + + constexpr ConstIter end() const noexcept { return cend(); } + + void clear() + { + if (heads_) + { + std::allocator elem_allocator; + for (uint32_t i = 0; i < buckets_; i++) + { + auto itr = heads_[i]; + while (itr != nullptr) + { + auto nextitr = itr->next; + itr->~Elem(); + elem_allocator.deallocate(itr, 1); + itr = nextitr; + } + } + + std::allocator buckets_allocator; + buckets_allocator.deallocate(heads_, buckets_); + } + heads_ = nullptr; + size_ = 0; + } + + Iter find(const K& key) + { + if (buckets_ == 0 || heads_ == nullptr) + { + return end(); + } + + uint32_t bucket = (hasher_)(key) % buckets_; + for (Elem* p = heads_[bucket]; p != nullptr; p = p->next) + { + if ((key_equal_)(p->entry.first, key)) + { + return Iter { this, bucket, p }; + } + }; + return end(); + } + + ConstIter find(const K& key) const + { + Iter iter = const_cast(this)->find(key); + return ConstIter { iter }; + } + + std::pair insert(const Entry& value) + { + Entry copy = value; + return insert(std::move(copy)); + } + + void rehash(uint32_t count) + { + count = size_ > count ? size_ : count; + HashMap rehashed { count }; + for (Iter itr = begin(); itr != end();) + { + Iter itrcopy = itr; + ++itr; + + uint32_t oldbucket = itrcopy.bucket_; + if (heads_[oldbucket] == itrcopy.cur_) + { + heads_[oldbucket] = nullptr; + } + itrcopy.self_ = &rehashed; + itrcopy.bucket_ = (rehashed.hasher_)(itrcopy.cur_->entry.first) % count; + Elem* p = rehashed.heads_[itrcopy.bucket_]; + if (p == nullptr) + { + p = rehashed.heads_[itrcopy.bucket_] = itrcopy.cur_; + size_ -= 1; + rehashed.size_ += 1; + + if (p->next) + { + p->next->prev = nullptr; + } + if (p->prev) + { + p->prev->next = nullptr; + } + p->next = nullptr; + p->prev = nullptr; + } + else + { + for (; p != nullptr; p = p->next) + { + if (p->next != nullptr) + { + continue; + } + p->next = itrcopy.cur_; + size_ -= 1; + rehashed.size_ += 1; + p->next->prev = p; + p->next->next = nullptr; + break; + } + } + } + + if (heads_) + { + std::allocator buckets_allocator; + buckets_allocator.deallocate(heads_, buckets_); + heads_ = nullptr; + } + + *this = std::move(rehashed); + } + + std::pair insert(Entry&& value) + { + std::pair ret { end(), false }; + std::allocator allocator; + + init_if_needed(); + + rehash_if_needed(size_ + 1); + + uint32_t bucket = (hasher_)(value.first) % buckets_; + Elem* p = heads_[bucket]; + if (p == nullptr) + { + // Make a new slot + ret.second = true; + Elem* newslot = allocator.allocate(1); + newslot->prev = nullptr; + newslot->next = nullptr; + heads_[bucket] = newslot; + new (&newslot->entry) Entry { std::move(value) }; + size_++; + ret.first = Iter { this, bucket, newslot }; + return ret; + } + + for(; p != nullptr;) + { + if ((key_equal_)(p->entry.first, value.first)) + { + ret.second = false; + ret.first = Iter { this, bucket, p }; + return ret; + } + + if (p->next == nullptr) + { + // Make a new slot + ret.second = true; + Elem* newslot = allocator.allocate(1); + newslot->prev = p; + newslot->next = nullptr; + p->next = newslot; + new (&newslot->entry) Entry { std::move(value) }; + size_++; + ret.first = Iter { this, bucket, newslot }; + return ret; + } + p = p->next; + } + + return ret; + } + + template + std::pair insert_or_assign(const K& key, M&& value) + { + K kcopy = key; + return insert_or_assign(std::move(kcopy), std::forward(value)); + } + + template + std::pair insert_or_assign(K&& key, M&& value) + { + std::pair ret { find(key), false }; + if (ret.first != end()) + { + ret.first->second = std::forward(value); + } + else + { + ret = insert({ std::move(key), std::forward(value) }); + } + return ret; + } + + template + std::pair emplace(Args&&... args) + { + Entry entry { std::forward(args)... }; + return insert(std::move(entry)); + } + + template + std::pair try_emplace(const K& key, Args&&... args) + { + Iter itr = find(key); + if (itr != end()) + { + return { itr, false }; + } + + return insert({ key, V(std::forward(args)...) }); + } + + template + std::pair try_emplace(K&& key, Args... args) + { + std::pair ret { end(), false }; + return insert({ std::move(key), V(std::forward(args)...)}); + } + + V& operator[](const K& key) + { + K copy { key }; + return this->operator[](std::move(copy)); + } + + V& operator[](K&& key) + { + std::allocator allocator; + init_if_needed(); + + rehash_if_needed(size_ + 1); + + uint32_t bucket = (hasher_)(key) % buckets_; + Elem* p = heads_[bucket]; + if (p == nullptr) + { + // Make a new slot + Elem* newslot = allocator.allocate(1); + newslot->prev = nullptr; + newslot->next = nullptr; + heads_[bucket] = newslot; + new (&newslot->entry) Entry { std::move(key), {} }; + size_++; + return newslot->entry.second; + } + for (; p != nullptr;) + { + if ((key_equal_)(p->entry.first, key)) + { + return p->entry.second; + } + + if (p->next == nullptr) + { + // Make a new slot + Elem* newslot = allocator.allocate(1); + newslot->prev = p; + newslot->next = nullptr; + p->next = newslot; + new (&newslot->entry) Entry { std::move(key), {} }; + size_++; + return newslot->entry.second; + } + p = p->next; + } + return end()->second; + } + + V& at(const K& key) + { + auto itr = find(key); + if (itr == end()) + { + throw std::out_of_range("key not found"); + } + return itr->second; + } + + const V& at(const K& key) const + { + auto itr = find(key); + if (itr == cend()) + { + throw std::out_of_range("key not found"); + } + return itr->second; + } + + Iter erase(Iter pos) + { + Iter ret = pos; + if (pos == end()) + { + return end(); + } + + std::allocator allocator; + uint32_t bucket = (hasher_)(pos.cur_->entry.first) % buckets_; + Elem* p = heads_[bucket]; + for (; p != nullptr;) + { + if ((key_equal_)(p->entry.first, pos.cur_->entry.first)) + { + // found it; remove, return next iter + ++ret; + if (p->next != nullptr) + { + p->next->prev = p->prev; + } + if (p->prev != nullptr) + { + p->prev->next = p->next; + } + if (p == heads_[bucket]) + { + heads_[bucket] = p->next; + } + p->~Elem(); + allocator.deallocate(p, 1); + size_ -= 1; + return ret; + } + p = p->next; + } + return ret; + } + + Iter erase(ConstIter pos) + { + return erase(pos.iter_); + } + + uint32_t erase(const K& key) + { + Iter pos = find(key); + if (pos != end()) + { + erase(pos); + return 1; + } + return 0; + } +}; + +} // namespace srb2 + +#endif // SRB2_CORE_HASH_MAP_HPP diff --git a/src/core/hash_set.cpp b/src/core/hash_set.cpp new file mode 100644 index 000000000..72c0dd5da --- /dev/null +++ b/src/core/hash_set.cpp @@ -0,0 +1,32 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "hash_set.hpp" + +#include + +#include "string.h" + +namespace srb2 +{ + +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; + +} // namespace srb2 diff --git a/src/core/hash_set.hpp b/src/core/hash_set.hpp new file mode 100644 index 000000000..ed2a714a0 --- /dev/null +++ b/src/core/hash_set.hpp @@ -0,0 +1,214 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_HASH_SET_HPP +#define SRB2_CORE_HASH_SET_HPP + +#include +#include + +#include "hash_map.hpp" +#include "string.h" + +namespace srb2 +{ + +template +class HashSet +{ + using Inner = HashMap; + +public: + class Iter + { + typename Inner::Iter iter_; + + Iter(typename Inner::Iter iter) : iter_(iter) {} + + friend class HashSet; + friend class ConstIter; + + public: + Iter() = default; + Iter(const Iter&) = default; + Iter(Iter&&) noexcept = default; + ~Iter() = default; + + Iter& operator=(const Iter&) = default; + Iter& operator=(Iter&&) noexcept = default; + + bool operator==(const Iter& r) const noexcept { return iter_ == r.iter_; } + bool operator!=(const Iter& r) const noexcept { return !(*this == r); } + + V& operator*() const noexcept { return iter_->first; } + V* operator->() const noexcept { return &iter_->first; } + Iter& operator++() noexcept + { + ++iter_; + return *this; + } + Iter operator++(int) noexcept + { + Iter self = *this; + ++*this; + return self; + } + }; + + class ConstIter + { + typename Inner::ConstIter iter_; + + friend class HashSet; + friend class Iter; + + public: + ConstIter() = default; + ConstIter(const ConstIter&) = default; + ConstIter(ConstIter&&) noexcept = default; + ~ConstIter() = default; + + ConstIter(const Iter& iter) : iter_(iter.iter_) {} + ConstIter(const typename Inner::ConstIter& iter) : iter_(iter) {} + + ConstIter& operator=(const ConstIter&) = default; + ConstIter& operator=(ConstIter&&) noexcept = default; + + bool operator==(const ConstIter& r) const noexcept { return iter_ == r.iter_; } + bool operator!=(const ConstIter& r) const noexcept { return !(*this == r); } + + const V& operator*() const noexcept { return iter_->first; } + const V* operator->() const noexcept { return &iter_->first; } + ConstIter& operator++() noexcept + { + ++iter_; + return *this; + } + ConstIter operator++(int) noexcept + { + ConstIter self = *this; + ++*this; + return self; + } + }; + +private: + Inner map_; + +public: + using key_type = V; + using value_type = V; + using size_type = typename Inner::size_type; + using difference_type = typename Inner::difference_type; + using hasher = typename Inner::hasher; + using key_equal = typename Inner::key_equal; + using reference = V&; + using const_reference = const V&; + using pointer = V*; + using const_pointer = const V*; + using iterator = Iter; + using const_iterator = ConstIter; + + HashSet() = default; + HashSet(const HashSet&) = default; + HashSet(HashSet&&) noexcept = default; + ~HashSet() = default; + + HashSet& operator=(const HashSet&) = default; + HashSet& operator=(HashSet&&) noexcept = default; + + Iter begin() { return map_.begin(); } + Iter end() { return map_.end(); } + ConstIter cbegin() const { return map_.cbegin(); } + ConstIter cend() const { return map_.cend(); } + ConstIter begin() const { return cbegin(); } + ConstIter end() const { return cend(); } + + bool empty() const noexcept { return map_.empty(); } + size_t size() const noexcept { return map_.size(); } + + void clear() { map_.clear(); } + + std::pair insert(const V& value) + { + V copy { value }; + return insert(std::move(copy)); + } + + std::pair insert(V&& value) + { + return emplace(std::move(value)); + } + + template + std::pair emplace(Args&&... args) + { + std::pair res; + auto [map_iter, map_inserted] = map_.emplace(std::forward(args)..., 0); + res.first = map_iter; + res.second = map_inserted; + return res; + } + + Iter erase(Iter pos) + { + auto map_ret = map_.erase(pos.iter_); + return map_ret; + } + + Iter erase(ConstIter pos) + { + auto map_ret = map_.erase(pos.iter_); + return map_ret; + } + + Iter erase(ConstIter first, ConstIter last) + { + ConstIter iter; + for (iter = first; iter != last;) + { + iter = erase(iter); + } + return typename Inner::Iter(iter.iter_); + } + + Iter find(const V& value) + { + auto map_ret = map_.find(value); + return map_ret; + } + + ConstIter find(const V& value) const + { + auto map_ret = map_.find(value); + return map_ret; + } + + void rehash(size_t count) + { + map_.rehash(count); + } +}; + +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; + +} // namespace srb2 + +#endif // SRB2_CORE_HASH_SET_HPP diff --git a/src/core/json.cpp b/src/core/json.cpp new file mode 100644 index 000000000..d123637e3 --- /dev/null +++ b/src/core/json.cpp @@ -0,0 +1,1809 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "json.hpp" + +#include +#include +#include +#include + +#include + +using namespace srb2; + +JsonError::JsonError(const std::string& what) : std::runtime_error(what) {} +JsonError::JsonError(const char* what) : std::runtime_error(what) {} +JsonError::JsonError(const JsonError&) noexcept = default; +JsonParseError::JsonParseError(const std::string& what) : JsonError(what) {} +JsonParseError::JsonParseError(const char* what) : JsonError(what) {} +JsonParseError::JsonParseError(const JsonParseError&) noexcept = default; + +JsonValue::JsonValue() + : type_(Type::kNull) +{} + +JsonValue::JsonValue(const JsonValue& r) + : type_(Type::kNull) +{ + *this = r; +} + +JsonValue::JsonValue(JsonValue&& r) noexcept + : type_(Type::kNull) +{ + *this = std::move(r); +} + +JsonValue& JsonValue::operator=(const JsonValue& r) +{ + switch (type_) + { + case Type::kString: + string_.~String(); + break; + case Type::kArray: + array_.~JsonArray(); + break; + case Type::kObject: + object_.~JsonObject(); + break; + default: + break; + } + type_ = r.type_; + switch (type_) + { + case Type::kNull: + break; + case Type::kBoolean: + boolean_ = r.boolean_; + break; + case Type::kNumber: + number_ = r.number_; + break; + case Type::kString: + new (&string_) String(r.string_); + break; + case Type::kArray: + new (&array_) JsonArray(r.array_); + break; + case Type::kObject: + new (&object_) JsonObject(r.object_); + break; + } + return *this; +} + +JsonValue& JsonValue::operator=(JsonValue&& r) noexcept +{ + switch (type_) + { + case Type::kString: + string_.~String(); + break; + case Type::kArray: + array_.~JsonArray(); + break; + case Type::kObject: + object_.~JsonObject(); + break; + default: + break; + } + type_ = r.type_; + switch (type_) + { + case Type::kBoolean: + boolean_ = r.boolean_; + break; + case Type::kNumber: + number_ = r.number_; + break; + case Type::kString: + new (&string_) String(std::move(r.string_)); + break; + case Type::kArray: + new (&array_) JsonArray(std::move(r.array_)); + break; + case Type::kObject: + new (&object_) JsonObject(std::move(r.object_)); + break; + default: + break; + } + switch (r.type_) + { + case Type::kString: + r.string_.~String(); + break; + case Type::kArray: + r.array_.~JsonArray(); + break; + case Type::kObject: + r.object_.~JsonObject(); + default: + break; + } + r.type_ = Type::kNull; + return *this; +} + +JsonValue::~JsonValue() +{ + switch (type_) + { + case Type::kString: + string_.~String(); + break; + case Type::kArray: + array_.~JsonArray(); + break; + case Type::kObject: + object_.~JsonObject(); + break; + default: + break; + } + type_ = Type::kNull; +} + +template <> bool JsonValue::get() const { return type_ == Type::kBoolean ? boolean_ : false; } +template <> int8_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> int16_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> int32_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> int64_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> uint8_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> uint16_t JsonValue::get() const { return type_ == Type::kNumber ? (uint16_t)number_ : 0; } +template <> uint32_t JsonValue::get() const { return type_ == Type::kNumber ? (uint32_t)number_ : 0; } +template <> uint64_t JsonValue::get() const { return type_ == Type::kNumber ? (uint64_t)number_ : 0; } +template <> float JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> double JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> String JsonValue::get() const { return type_ == Type::kString ? string_ : String(); } +template <> std::string JsonValue::get() const { return type_ == Type::kString ? static_cast(string_) : std::string(); } +template <> std::string_view JsonValue::get() const { return type_ == Type::kString ? static_cast(string_) : std::string_view(); } +template <> JsonArray JsonValue::get() const { return type_ == Type::kArray ? array_ : JsonArray(); } +template <> JsonObject JsonValue::get() const { return type_ == Type::kObject ? object_ : JsonObject(); } +template <> JsonValue JsonValue::get() const { return *this; } + +JsonArray& JsonValue::as_array() +{ + if (!is_array()) throw JsonError("accessing non-array as array"); + return array_; +} + +const JsonArray& JsonValue::as_array() const +{ + if (!is_array()) throw JsonError("accessing non-array as array"); + return array_; +} + +JsonObject& JsonValue::as_object() +{ + if (!is_object()) throw JsonError("accessing non-object as object"); + return object_; +} + +const JsonObject& JsonValue::as_object() const +{ + if (!is_object()) throw JsonError("accessing non-array as object"); + return object_; +} + +JsonValue& JsonValue::at(uint32_t i) +{ + JsonArray& arr = as_array(); + return arr.at(i); +} + +const JsonValue& JsonValue::at(uint32_t i) const +{ + const JsonArray& arr = as_array(); + return arr.at(i); +} + +JsonValue& JsonValue::at(const char* key) +{ + JsonObject& obj = as_object(); + return obj.at(key); +} + +JsonValue& JsonValue::at(const String& key) +{ + JsonObject& obj = as_object(); + return obj.at(key); +} + +const JsonValue& JsonValue::at(const char* key) const +{ + const JsonObject& obj = as_object(); + return obj.at(key); +} + +const JsonValue& JsonValue::at(const String& key) const +{ + const JsonObject& obj = as_object(); + return obj.at(key); +} + +JsonValue& JsonValue::operator[](uint32_t i) +{ + if (is_null() && !is_array()) + { + *this = JsonArray(); + } + else if (!is_array()) + { + throw JsonError("indexing non-array as array"); + } + JsonArray& arr = as_array(); + return arr[i]; +} + +JsonValue& JsonValue::operator[](const char* key) +{ + if (is_null() && !is_object()) + { + *this = JsonObject(); + } + else if (!is_object()) + { + throw JsonError("indexing non-object as object"); + } + JsonObject& obj = as_object(); + return obj[key]; +} + +JsonValue& JsonValue::operator[](const String& key) +{ + if (is_null() && !is_object()) + { + *this = JsonObject(); + } + else if (!is_object()) + { + throw JsonError("indexing non-object as object"); + } + JsonObject& obj = as_object(); + return obj[key]; +} + +bool JsonValue::contains(const String& key) const +{ + return contains(key.c_str()); +} + +bool JsonValue::contains(const char* key) const +{ + if (!is_object()) + { + return false; + } + const JsonObject& obj = as_object(); + return obj.find(key) != obj.end(); +} + +void JsonValue::to_json(JsonValue& to) const +{ + to = *this; +} + +void JsonValue::from_json(const JsonValue& from) +{ + *this = from; +} + +String JsonValue::to_json_string() const +{ + String ret; + + switch (type_) + { + case Type::kNull: + ret = "null"; + break; + case Type::kBoolean: + ret = boolean_ ? "true" : "false"; + break; + case Type::kNumber: + if (std::isnan(number_) || std::isinf(number_)) + { + ret = "null"; + break; + } + ret = fmt::format("{}", number_); + break; + case Type::kString: + ret = "\""; + for (auto c : string_) + { + switch (c) + { + case '"': ret.append("\\\""); break; + case '\\': ret.append("\\\\"); break; + case '\b': ret.append("\\b"); break; + case '\f': ret.append("\\f"); break; + case '\n': ret.append("\\n"); break; + case '\r': ret.append("\\r"); break; + case '\t': ret.append("\\t"); break; + default: + if (c >= 0x00 && c <= 0x1f) + { + ret.append(fmt::format("\\u{:04x}", c)); + } + else + { + ret.push_back(c); + } + } + } + ret.append("\""); + break; + case Type::kArray: + { + ret = "["; + for (auto itr = array_.begin(); itr != array_.end(); itr++) + { + ret.append(JsonValue(*itr).to_json_string()); + if (std::next(itr) != array_.end()) + { + ret.append(","); + } + } + ret.append("]"); + break; + } + case Type::kObject: + { + ret = "{"; + for (auto itr = object_.begin(); itr != object_.end(); itr++) + { + ret.append(JsonValue(itr->first).to_json_string()); + ret.append(":"); + ret.append(JsonValue(itr->second).to_json_string()); + auto next = itr; + ++next; + if (next != object_.end()) + { + ret.append(","); + } + } + ret.append("}"); + return ret; + } + } + return ret; +} + +srb2::Vector JsonValue::to_ubjson() const +{ + srb2::Vector out; + + do_to_ubjson(out); + + return out; +} + +void JsonValue::value_to_ubjson(srb2::Vector& out) +{ + out.push_back(std::byte { 'Z' }); +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, bool value) +{ + out.push_back(value ? std::byte { 'T' } : std::byte { 'F' }); +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, double value) +{ + if (std::isnan(value) || std::isinf(value)) + { + out.push_back(std::byte { 'Z' }); + return; + } + + uint64_t num_as_int; + std::byte buf[8]; + out.push_back(std::byte { 'D' }); + std::memcpy(&num_as_int, &value, sizeof(double)); + buf[0] = (std::byte)((num_as_int & 0xFF00000000000000) >> 56); + buf[1] = (std::byte)((num_as_int & 0x00FF000000000000) >> 48); + buf[2] = (std::byte)((num_as_int & 0x0000FF0000000000) >> 40); + buf[3] = (std::byte)((num_as_int & 0x000000FF00000000) >> 32); + buf[4] = (std::byte)((num_as_int & 0x00000000FF000000) >> 24); + buf[5] = (std::byte)((num_as_int & 0x0000000000FF0000) >> 16); + buf[6] = (std::byte)((num_as_int & 0x000000000000FF00) >> 8); + buf[7] = (std::byte)((num_as_int & 0x00000000000000FF)); + for (int i = 0; i < 8; i++) + { + out.push_back(buf[i]); + } +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, uint64_t value) +{ + + std::byte buf[8]; + int64_t string_len_i64; + int32_t string_len_32; + int16_t string_len_16; + uint8_t string_len_8; + if (value < std::numeric_limits().max()) + { + string_len_8 = value; + out.push_back(std::byte { 'U' }); + out.push_back((std::byte)(string_len_8)); + } + else if (value < std::numeric_limits().max()) + { + string_len_16 = value; + buf[0] = (std::byte)((string_len_16 & 0xFF00) >> 8); + buf[1] = (std::byte)((string_len_16 & 0x00FF) >> 0); + out.push_back(std::byte { 'I' }); + out.push_back(buf[0]); + out.push_back(buf[1]); + } + else if (value < std::numeric_limits().max()) + { + string_len_32 = value; + buf[0] = (std::byte)((string_len_32 & 0xFF000000) >> 24); + buf[1] = (std::byte)((string_len_32 & 0x00FF0000) >> 16); + buf[2] = (std::byte)((string_len_32 & 0x0000FF00) >> 8); + buf[3] = (std::byte)((string_len_32 & 0x000000FF)); + out.push_back(std::byte { 'l' }); + out.push_back(buf[0]); + out.push_back(buf[1]); + out.push_back(buf[2]); + out.push_back(buf[3]); + } + else if (value < std::numeric_limits().max()) + { + string_len_i64 = value; + buf[0] = (std::byte)((string_len_i64 & 0xFF00000000000000) >> 56); + buf[1] = (std::byte)((string_len_i64 & 0x00FF000000000000) >> 48); + buf[2] = (std::byte)((string_len_i64 & 0x0000FF0000000000) >> 40); + buf[3] = (std::byte)((string_len_i64 & 0x000000FF00000000) >> 32); + buf[4] = (std::byte)((string_len_i64 & 0x00000000FF000000) >> 24); + buf[5] = (std::byte)((string_len_i64 & 0x0000000000FF0000) >> 16); + buf[6] = (std::byte)((string_len_i64 & 0x000000000000FF00) >> 8); + buf[7] = (std::byte)((string_len_i64 & 0x00000000000000FF)); + out.push_back(std::byte { 'L' }); + out.push_back(buf[0]); + out.push_back(buf[1]); + out.push_back(buf[2]); + out.push_back(buf[3]); + out.push_back(buf[4]); + out.push_back(buf[5]); + out.push_back(buf[6]); + out.push_back(buf[7]); + } + else + { + throw JsonParseError("inexpressible integer"); + } +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, const String& value, bool include_type) +{ + if (include_type) + { + out.push_back(std::byte { 'S' }); + } + + value_to_ubjson(out, static_cast(value.size())); + for (auto c : value) + { + out.push_back((std::byte)(c)); + } +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, const JsonArray& value) +{ + out.push_back(std::byte { '[' }); + if (value.empty()) + { + out.push_back(std::byte { ']' }); + return; + } + + out.push_back(std::byte { '#' }); + value_to_ubjson(out, (uint64_t)value.size()); + for (auto& v : value) + { + v.do_to_ubjson(out); + } + + // Don't emit end because we included size prefix +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, const JsonObject& value) +{ + out.push_back(std::byte { '{' }); + if (value.empty()) + { + out.push_back(std::byte { '}' }); + return; + } + + out.push_back(std::byte { '#' }); + value_to_ubjson(out, static_cast(value.size())); + for (auto& v : value) + { + value_to_ubjson(out, v.first, false); + v.second.do_to_ubjson(out); + } + + // Don't emit end because we included size prefix +} + +void JsonValue::do_to_ubjson(srb2::Vector& out) const +{ + switch (type_) + { + case Type::kNull: + value_to_ubjson(out); + break; + case Type::kBoolean: + value_to_ubjson(out, boolean_); + break; + case Type::kNumber: + value_to_ubjson(out, number_); + break; + case Type::kString: + value_to_ubjson(out, string_, true); + break; + case Type::kArray: + value_to_ubjson(out, array_); + break; + case Type::kObject: + value_to_ubjson(out, object_); + break; + default: + break; + } +} + +static uint8_t u8_from_ubjson(tcb::span& ubjson) +{ + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + uint8_t ret = std::to_integer(ubjson[0]); + ubjson = ubjson.subspan(1); + return ret; +} + +static int8_t i8_from_ubjson(tcb::span& ubjson) +{ + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + int8_t ret = std::to_integer(ubjson[0]); + ubjson = ubjson.subspan(1); + return ret; +} + +static int16_t i16_from_ubjson(tcb::span& ubjson) +{ + uint8_t b[2]; + uint16_t native; + int16_t ret; + if (ubjson.size() < 2) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + native = b[0] << 8 | b[1]; + std::memcpy(&ret, &native, 2); + ubjson = ubjson.subspan(2); + return ret; +} + +static int32_t i32_from_ubjson(tcb::span& ubjson) +{ + uint8_t b[4]; + uint32_t native; + int32_t ret; + if (ubjson.size() < 4) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + native = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; + std::memcpy(&ret, &native, 4); + ubjson = ubjson.subspan(4); + return ret; +} + +static int64_t i64_from_ubjson(tcb::span& ubjson) +{ + uint64_t b[8]; + uint64_t native; + int64_t ret; + if (ubjson.size() < 8) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + b[4] = std::to_integer(ubjson[4]); + b[5] = std::to_integer(ubjson[5]); + b[6] = std::to_integer(ubjson[6]); + b[7] = std::to_integer(ubjson[7]); + native = b[0] << 56 | b[1] << 48 | b[2] << 40 | b[3] << 32 | b[4] << 24 | b[5] << 16 | b[6] << 8 | b[7]; + std::memcpy(&ret, &native, 8); + ubjson = ubjson.subspan(8); + return ret; +} + +static float f32_from_ubjson(tcb::span& ubjson) +{ + uint8_t b[8]; + uint32_t native; + float ret; + if (ubjson.size() < 4) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + native = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; + std::memcpy(&ret, &native, 4); + ubjson = ubjson.subspan(4); + return ret; +} + +static double f64_from_ubjson(tcb::span& ubjson) +{ + uint64_t b[8]; + uint64_t native; + double ret; + if (ubjson.size() < 8) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + b[4] = std::to_integer(ubjson[4]); + b[5] = std::to_integer(ubjson[5]); + b[6] = std::to_integer(ubjson[6]); + b[7] = std::to_integer(ubjson[7]); + native = b[0] << 56 | b[1] << 48 | b[2] << 40 | b[3] << 32 | b[4] << 24 | b[5] << 16 | b[6] << 8 | b[7]; + std::memcpy(&ret, &native, 8); + ubjson = ubjson.subspan(8); + return ret; +} + +static uint64_t length_from_ubjson(tcb::span& ubjson) +{ + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + uint64_t size = 0; + switch ((char)(ubjson[0])) + { + case 'i': + ubjson = ubjson.subspan(1); + size = i8_from_ubjson(ubjson); + break; + case 'U': + ubjson = ubjson.subspan(1); + size = u8_from_ubjson(ubjson); + break; + case 'I': + ubjson = ubjson.subspan(1); + size = i16_from_ubjson(ubjson); + break; + case 'l': + ubjson = ubjson.subspan(1); + size = i32_from_ubjson(ubjson); + break; + case 'L': + ubjson = ubjson.subspan(1); + size = i64_from_ubjson(ubjson); + break; + default: + throw JsonParseError("illegal data length type"); + } + return size; +} + +static String string_from_ubjson(tcb::span& ubjson) +{ + uint64_t len = length_from_ubjson(ubjson); + if (len > std::numeric_limits().max()) + { + throw JsonParseError("unloadable string length"); + } + String ret; + for (size_t i = 0; i < len; i++) + { + + } + auto strdata = ubjson.subspan(0, (size_t)len); + ubjson = ubjson.subspan((size_t)len); + for (auto itr = strdata.begin(); itr != strdata.end(); itr++) + { + ret.push_back((char)(*itr)); + } + return ret; +} + +template +static void read_ubjson_array_elements(F f, JsonArray& arr, tcb::span& ubjson, uint64_t len) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + arr.push_back((f)(ubjson)); + } +} + +template +static void read_ubjson_array_elements(F f, JsonArray& arr, tcb::span& ubjson, uint64_t len, int depth) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + arr.push_back((f)(ubjson, depth)); + } +} + +static JsonValue do_from_ubjson(tcb::span& ubjson, int depth); +static JsonObject object_from_ubjson(tcb::span& ubjson, int depth); + +static JsonArray array_from_ubjson(tcb::span& ubjson, int depth) +{ + char typecode = 0; + if ((char)(ubjson[0]) == '$') + { + if (ubjson.size() < 2) throw JsonParseError("insufficient data"); + typecode = (char)(ubjson[1]); + ubjson = ubjson.subspan(2); + } + bool has_len = false; + uint64_t len = 0; + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + if ((char)(ubjson[0]) == '#') + { + ubjson = ubjson.subspan(1); + has_len = true; + len = length_from_ubjson(ubjson); + } + JsonArray ret; + if (has_len) + { + ret.reserve(len); + } + if (typecode != 0) + { + switch (typecode) + { + case 'i': + read_ubjson_array_elements(i8_from_ubjson, ret, ubjson, len); + break; + case 'U': + read_ubjson_array_elements(u8_from_ubjson, ret, ubjson, len); + break; + case 'I': + read_ubjson_array_elements(i16_from_ubjson, ret, ubjson, len); + break; + case 'l': + read_ubjson_array_elements(i32_from_ubjson, ret, ubjson, len); + break; + case 'L': + read_ubjson_array_elements(i64_from_ubjson, ret, ubjson, len); + break; + case 'd': + read_ubjson_array_elements(f32_from_ubjson, ret, ubjson, len); + break; + case 'D': + read_ubjson_array_elements(f64_from_ubjson, ret, ubjson, len); + break; + case 'S': + read_ubjson_array_elements(string_from_ubjson, ret, ubjson, len); + break; + case '[': + read_ubjson_array_elements(array_from_ubjson, ret, ubjson, len, depth + 1); + break; + case '{': + read_ubjson_array_elements(object_from_ubjson, ret, ubjson, len, depth + 1); + break; + default: + throw JsonParseError("invalid typecode for array"); + } + } + else + { + if (has_len) + { + for (uint64_t i = 0; i < len; i++) + { + JsonValue v = do_from_ubjson(ubjson, depth); + ret.push_back(std::move(v)); + } + } + else + { + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + while ((char)(ubjson[0]) != ']') + { + JsonValue v = do_from_ubjson(ubjson, depth); + ret.push_back(std::move(v)); + } + ubjson = ubjson.subspan(1); + } + } + return ret; +} + +template +static void read_ubjson_object_elements(F f, JsonObject& obj, tcb::span& ubjson, uint64_t len) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + String key = string_from_ubjson(ubjson); + JsonValue value = (f)(ubjson); + obj[std::move(key)] = std::move(value); + } +} + +template +static void read_ubjson_object_elements(F f, JsonObject& obj, tcb::span& ubjson, uint64_t len, int depth) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + String key = string_from_ubjson(ubjson); + JsonValue value = (f)(ubjson, depth); + obj[std::move(key)] = std::move(value); + } +} + +static JsonObject object_from_ubjson(tcb::span& ubjson, int depth) +{ + char typecode = 0; + if ((char)(ubjson[0]) == '$') + { + if (ubjson.size() < 2) throw JsonParseError("insufficient data"); + typecode = (char)(ubjson[1]); + ubjson = ubjson.subspan(2); + } + bool has_len = false; + uint64_t len = 0; + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + if ((char)(ubjson[0]) == '#') + { + ubjson = ubjson.subspan(1); + has_len = true; + len = length_from_ubjson(ubjson); + } + JsonObject ret; + if (has_len) + { + ret.rehash(len); + } + if (typecode != 0) + { + switch (typecode) + { + case 'i': + read_ubjson_object_elements(i8_from_ubjson, ret, ubjson, len); + break; + case 'U': + read_ubjson_object_elements(u8_from_ubjson, ret, ubjson, len); + break; + case 'I': + read_ubjson_object_elements(i16_from_ubjson, ret, ubjson, len); + break; + case 'l': + read_ubjson_object_elements(i32_from_ubjson, ret, ubjson, len); + break; + case 'L': + read_ubjson_object_elements(i64_from_ubjson, ret, ubjson, len); + break; + case 'd': + read_ubjson_object_elements(f32_from_ubjson, ret, ubjson, len); + break; + case 'D': + read_ubjson_object_elements(f64_from_ubjson, ret, ubjson, len); + break; + case 'S': + read_ubjson_object_elements(string_from_ubjson, ret, ubjson, len); + break; + case '[': + read_ubjson_object_elements(array_from_ubjson, ret, ubjson, len, depth + 1); + break; + case '{': + read_ubjson_object_elements(object_from_ubjson, ret, ubjson, len, depth + 1); + break; + default: + throw JsonParseError("invalid typecode for array"); + } + } + else + { + if (has_len) + { + for (uint64_t i = 0; i < len; i++) + { + String key = string_from_ubjson(ubjson); + JsonValue value = do_from_ubjson(ubjson, depth); + ret[std::move(key)] = std::move(value); + } + } + else + { + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + while ((char)(ubjson[0]) != '}') + { + String key = string_from_ubjson(ubjson); + JsonValue value = do_from_ubjson(ubjson, depth); + ret[std::move(key)] = std::move(value); + } + ubjson = ubjson.subspan(1); + } + } + return ret; +} + +static JsonValue do_from_ubjson(tcb::span& ubjson, int depth) +{ + if (depth > 1000) + { + throw JsonParseError("ubjson depth limit exceeded"); + } + + if (ubjson.empty()) + { + throw JsonParseError("empty ubjson payload"); + } + + char typecode = (char)(ubjson[0]); + switch (typecode) + { + case 'Z': + ubjson = ubjson.subspan(1); + return JsonValue(); + case 'F': + ubjson = ubjson.subspan(1); + return JsonValue(false); + case 'T': + ubjson = ubjson.subspan(1); + return JsonValue(true); + case 'i': + ubjson = ubjson.subspan(1); + return i8_from_ubjson(ubjson); + case 'U': + ubjson = ubjson.subspan(1); + return u8_from_ubjson(ubjson); + case 'I': + ubjson = ubjson.subspan(1); + return i16_from_ubjson(ubjson); + case 'l': + ubjson = ubjson.subspan(1); + return i32_from_ubjson(ubjson); + case 'L': + ubjson = ubjson.subspan(1); + return i64_from_ubjson(ubjson); + case 'd': + ubjson = ubjson.subspan(1); + return f32_from_ubjson(ubjson); + case 'D': + ubjson = ubjson.subspan(1); + return f64_from_ubjson(ubjson); + case 'S': + ubjson = ubjson.subspan(1); + return string_from_ubjson(ubjson); + case '[': + ubjson = ubjson.subspan(1); + return array_from_ubjson(ubjson, depth); + case '{': + ubjson = ubjson.subspan(1); + return object_from_ubjson(ubjson, depth); + default: + throw JsonParseError(fmt::format("unrecognized ubjson typecode 0x{:02x}", typecode)); + } +} + +JsonValue JsonValue::from_ubjson(tcb::span ubjson) +{ + return do_from_ubjson(ubjson, 0); +} + +namespace +{ +struct Token +{ + enum class Type + { + kEof, + kOpenCurly, + kCloseCurly, + kColon, + kOpenSquare, + kCloseSquare, + kComma, + kBoolean, + kString, + kNumber, + kNull + }; + Type type = Type::kEof; + std::string_view slice; +}; + +class Tokenizer +{ + std::string_view in_; + + void consume(size_t len); +public: + Tokenizer(const std::string_view& in) : in_(in) {} + Token peek(); + Token next(); +}; + +Token Tokenizer::peek() +{ + Token ret; + while (!in_.empty() && ret.type == Token::Type::kEof) + { + unsigned char next = in_[0]; + switch (next) + { + case ' ': + case '\n': + case '\r': + case '\t': + in_.remove_prefix(1); + break; + case 'n': + if (in_.size() < 4) throw JsonParseError("reached end of buffer parsing null"); + ret = Token { Token::Type::kNull, in_.substr(0, 4) }; + if (ret.slice != "null") throw JsonParseError("invalid null token"); + break; + case 'f': + if (in_.size() < 5) throw JsonParseError("reached end of buffer parsing false"); + ret = Token { Token::Type::kBoolean, in_.substr(0, 5) }; + if (ret.slice != "false") throw JsonParseError("invalid boolean token"); + break; + case 't': + if (in_.size() < 4) throw JsonParseError("reached end of buffer parsing true"); + ret = Token { Token::Type::kBoolean, in_.substr(0, 4) }; + if (ret.slice != "true") throw JsonParseError("invalid boolean token"); + break; + case '{': + ret = Token { Token::Type::kOpenCurly, in_.substr(0, 1) }; + break; + case '}': + ret = Token { Token::Type::kCloseCurly, in_.substr(0, 1) }; + break; + case '[': + ret = Token { Token::Type::kOpenSquare, in_.substr(0, 1) }; + break; + case ']': + ret = Token { Token::Type::kCloseSquare, in_.substr(0, 1) }; + break; + case ':': + ret = Token { Token::Type::kColon, in_.substr(0, 1) }; + break; + case ',': + ret = Token { Token::Type::kComma, in_.substr(0, 1) }; + break; + case '"': + { + bool skipnextquote = false; + size_t len; + for (len = 1; len < in_.size(); len++) + { + char c = in_[len]; + bool shouldbreak = false; + switch (c) + { + case '\r': + throw JsonParseError("illegal carriage return in string literal"); + case '\n': + throw JsonParseError("illegal line feed in string literal"); + case '\\': + skipnextquote = true; + break; + case '"': + if (skipnextquote) skipnextquote = false; + else shouldbreak = true; + break; + default: + if (skipnextquote) skipnextquote = false; + } + if (shouldbreak) break; + } + if (in_[len] != '"') throw JsonParseError("found unterminated string"); + ret = Token { Token::Type::kString, in_.substr(0, len + 1) }; + break; + } + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + size_t len; + bool firstiszero = next == '0'; + bool zeroseen = next == '0'; + bool integerseen = next >= '0' && next <= '9'; + bool periodseen = false; + bool periodlast = false; + bool exponentseen = false; + for (len = 1; len < in_.size(); len++) + { + char c = in_[len]; + bool shouldbreak = false; + switch (c) + { + default: + shouldbreak = true; + break; + case '.': + if (periodseen || exponentseen || (!periodseen && !zeroseen && !integerseen)) + throw JsonParseError("unexpected period in number token"); + periodseen = true; + periodlast = true; + break; + case '0': + if (firstiszero && len == 1) throw JsonParseError("more than 1 preceding 0"); + if ((next == '-' || next == '+') && len == 1) firstiszero = true; + zeroseen = true; + integerseen = true; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (firstiszero && !periodseen && !exponentseen) throw JsonParseError("nonzero integral part of number preceded by 0"); + periodlast = false; + integerseen = true; + break; + case 'e': + case 'E': + if (exponentseen) throw JsonParseError("multiple exponent"); + exponentseen = true; + break; + case '+': + case '-': + if (!exponentseen && len != 0) throw JsonParseError("sign after number started"); + break; + } + if (shouldbreak) break; + } + if (periodlast) throw JsonParseError("real number without fractional part"); + ret = Token { Token::Type::kNumber, in_.substr(0, len) }; + break; + } + default: + throw JsonParseError(fmt::format("unexpected character {:c}", next)); + } + } + std::string copy { ret.slice }; + return ret; +} + +void Tokenizer::consume(size_t len) +{ + in_.remove_prefix(len); +} + +Token Tokenizer::next() +{ + Token peeked = peek(); + consume(peeked.slice.size()); + std::string p { peeked.slice }; + return peeked; +} + +} // namespace + +static JsonValue parse_value(Tokenizer& tokenizer, int depth); + +static JsonValue parse_number(const Token& token) +{ + std::string_view s { token.slice }; + if (s.empty()) throw JsonParseError("empty number token"); + while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n' || s[0] == '\r') + { + s.remove_prefix(1); + } + + bool negative = false; + if (s[0] == '-') + { + negative = true; + s.remove_prefix(1); + } + else if (s[0] == '+') + { + negative = false; + s.remove_prefix(1); + } + + if (s.empty()) + { + throw JsonParseError("only sign present on number"); + } + + const char* integral_start = s.begin(); + const char* integral_end; + const char* decimal = nullptr; + while (!s.empty()) + { + if (s[0] == '.') + { + decimal = s.begin(); + integral_end = s.begin(); + s.remove_prefix(1); + break; + } + else if (s[0] < '0' || s[0] > '9') + { + integral_end = s.begin() - 1; + break; + } + integral_end = s.begin() + 1; + s.remove_prefix(1); + } + + const char* decimal_start = s.end(); + const char* decimal_end = s.end(); + const char* exponent_start = s.end(); + const char* exponent_end = s.end(); + bool should_have_exponent = false; + if (decimal != nullptr && (decimal + 1) < s.end()) + { + decimal_start = decimal + 1; + } + while (!s.empty()) + { + // ingest decimal + if (s[0] == 'E' || s[0] == 'e') + { + if (decimal_start != s.end()) decimal_end = s.begin(); + exponent_start = s.begin() + 1; + should_have_exponent = true; + s.remove_prefix(1); + break; + } + else if ((s[0] < '0' || s[0] > '9') && s[0] != '+' && s[0] != '-') + { + throw JsonParseError("invalid character after decimal"); + } + decimal_end = s.begin() + 1; + s.remove_prefix(1); + } + + bool exponent_negative = false; + + if (should_have_exponent) + { + if (s.empty()) + { + throw JsonParseError("exponent started but not specified"); + } + bool exponent_was_signed = false; + while (!s.empty()) + { + if (s[0] == '-') + { + if (exponent_was_signed) throw JsonParseError("multiple signs on exponent"); + exponent_negative = true; + exponent_start++; + exponent_was_signed = true; + s.remove_prefix(1); + continue; + } + else if (s[0] == '+') + { + if (exponent_was_signed) throw JsonParseError("multiple signs on exponent"); + exponent_start++; + exponent_was_signed = true; + s.remove_prefix(1); + continue; + } + + if (s[0] < '0' || s[0] > '9') + { + throw JsonParseError("invalid character after exponent"); + } + exponent_end = s.begin() + 1; + s.remove_prefix(1); + } + if ((exponent_end - exponent_start) == 0) + { + throw JsonParseError("exponent started but not specified"); + } + } + + std::string_view integral_view { integral_start, (size_t)(integral_end - integral_start) }; + std::string_view decimal_view { decimal_start, (size_t)(decimal_end - decimal_start) }; + std::string_view exponent_view { exponent_start, (size_t)(exponent_end - exponent_start) }; + + if (should_have_exponent && decimal_start != s.end() && decimal_view.empty()) + { + throw JsonParseError("exponent after decimal but no decimal value"); + } + if (should_have_exponent && exponent_view.empty()) + { + throw JsonParseError("no exponent despite e/E +/-"); + } + // if (!exponent_negative && exponent_view == "1") + // { + // throw JsonParseError("exponent of 1 not allowed"); + // } + + double number = 0.0; + uint64_t integral_int = 0; + for (auto i : integral_view) + { + integral_int = 10 * integral_int + (i - '0'); + } + double decimal_value = 0.0; + for (auto i : decimal_view) + { + decimal_value = (decimal_value / 10) + ((double)(i - '0') / 10); + } + uint64_t exponent_int = 0; + for (auto i : exponent_view) + { + exponent_int = 10 * exponent_int + (i - '0'); + } + if (negative) + { + integral_int *= -1; + } + if (exponent_negative) + { + exponent_int *= -1; + } + number = std::pow(10, exponent_int) * (double)integral_int + decimal_value; + + return JsonValue(number); +} + +static char hexconv(char c) +{ + switch (c) + { + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + c += 32; + break; + default: + break; + } + switch (c) + { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': return 10; + case 'b': return 11; + case 'c': return 12; + case 'd': return 13; + case 'e': return 14; + case 'f': return 15; + default: throw JsonParseError("illegal unicode escape sequence character"); + } +} + +static constexpr bool is_surrogate(uint16_t code) +{ + return (0xf800 & code) == 0xd800; +} + +static constexpr bool is_high_surrogate(uint16_t code) +{ + return (code & 0xfc00) == 0xd800; +} + +static constexpr bool is_low_surrogate(uint16_t code) +{ + return (code & 0xfc00) == 0xdc00; +} + +static constexpr uint32_t merge_surrogate_pair(uint16_t low, uint16_t high) +{ + return ((high - 0xd800) << 10) + ((low - 0xdc00) + 0x10000); +} + +static JsonValue parse_string(const Token& token) +{ + std::string_view read { token.slice.substr(1, token.slice.size() - 2) }; + String conv; + for (auto itr = read.begin(); itr != read.end();) + { + int c = *itr & 0xFF; + switch (c) + { + case '\\': + { + // Reverse solidus indicates escape sequence + if (std::next(itr) == read.end()) throw JsonParseError("unterminated string escape sequence"); + char control = *++itr; + switch (control) + { + case '"': + case '\\': + case '/': + conv.push_back(control); + ++itr; + break; + case 'b': + conv.push_back('\b'); + ++itr; + break; + case 'f': + conv.push_back('\f'); + ++itr; + break; + case 'n': + conv.push_back('\n'); + ++itr; + break; + case 'r': + conv.push_back('\r'); + ++itr; + break; + case 't': + conv.push_back('\t'); + ++itr; + break; + case 'x': + { + if (std::distance(itr, read.end()) < 3) throw JsonParseError("unterminated hex char sequence"); + char hex[2]; + hex[0] = *++itr; + hex[1] = *++itr; + char byte = (hexconv(hex[0]) << 4) | hexconv(hex[1]); + if (byte < 0x20) throw JsonParseError("bad escaped control code"); + conv.push_back(byte); + ++itr; + break; + } + case 'u': + { + if (std::distance(itr, read.end()) < 5) throw JsonParseError("unterminated utf16 code sequence"); + // Next 4 characters are a hex sequence representing a UTF-16 surrogate + // We have to do silly things to make this work correctly + char hex[4]; + hex[0] = *++itr; + hex[1] = *++itr; + hex[2] = *++itr; + hex[3] = *++itr; + if (hex[0] == -1 || hex[1] == -1 || hex[2] == -1 || hex[3] == -1) + throw JsonParseError("invalid unicode escape"); + char byte[2]; + byte[0] = (hexconv(hex[0]) << 4) | hexconv(hex[1]); + byte[1] = (hexconv(hex[2]) << 4) | hexconv(hex[3]); + uint16_t utf16 = hexconv(hex[0]) << 12 | hexconv(hex[1]) << 8 | hexconv(hex[2]) << 4 | hexconv(hex[3]); + bool valid_codepoint = false; + uint32_t codepoint = 0; + if (is_low_surrogate(utf16)) + { + // invalid surrogate pair -- high must precede low + conv.push_back('\\'); + conv.push_back('u'); + conv.push_back(hex[0]); + conv.push_back(hex[1]); + conv.push_back(hex[2]); + conv.push_back(hex[3]); + } + else if (is_high_surrogate(utf16)) + { + if (std::distance(itr, read.end()) < 6) + { + // invalid surrogate pair -- high must precede low + conv += "\\u"; + } + else + { + // potentially valid... + if ( + *itr == '\\' && + *(itr + 1) == 'u' && + (hex[0] = *(itr + 2)) != -1 && + (hex[1] = *(itr + 3)) != -1 && + (hex[2] = *(itr + 4)) != -1 && + (hex[3] = *(itr + 5)) != -1 + ) + { + uint16_t utf16_2 = hexconv(hex[0]) << 12 | hexconv(hex[1]) << 8 | hexconv(hex[2]) << 4 | hexconv(hex[3]); + if (is_low_surrogate(utf16_2)) + { + itr += 6; + codepoint = merge_surrogate_pair(utf16_2, utf16); + valid_codepoint = true; + } + else + { + itr += 2; + conv += "\\u"; + } + } + else + { + conv += "\\u"; + } + } + } + else + { + // non-surrogate represents unicode codepoint + codepoint = utf16; + valid_codepoint = true; + } + + if (valid_codepoint) + { + char encoded[4]; + int len; + // encode codepoint as UTF-8 + if (codepoint <= 0x7f) + { + encoded[0] = codepoint; + len = 1; + } + else if (codepoint <= 0x7ff) + { + encoded[0] = 0300 | (codepoint >> 6); + encoded[1] = 0200 | (codepoint & 077); + len = 2; + } + else if (codepoint <= 0xffff) + { + if (is_surrogate(codepoint)) + { + codepoint = 0xfffd; + } + encoded[0] = 0340 | (codepoint >> 2); + encoded[1] = 0200 | ((codepoint >> 6) & 077); + encoded[2] = 0200 | (codepoint & 077); + len = 3; + } + else if (~(codepoint >> 18) & 007) + { + encoded[0] = 0360 | (codepoint >> 18); + encoded[1] = 0200 | ((codepoint >> 12) & 077); + encoded[2] = 0200 | ((c >> 6) & 077); + encoded[3] = 0200 | (c & 077); + len = 4; + } + else + { + encoded[0] = 0xef; + encoded[1] = 0xbf; + encoded[2] = 0xbd; + len = 3; + } + conv.append(encoded, len); + } + + ++itr; + break; + } + default: + throw JsonParseError("invalid string escape control code"); + } + break; + } + default: + if (c < 0x20) throw JsonParseError("unescaped control code"); + conv.push_back(c); + ++itr; + break; + } + } + return JsonValue(conv); +} + +static JsonValue parse_object(Tokenizer& tokenizer, int depth) +{ + JsonObject obj; + bool done = false; + if (tokenizer.peek().type == Token::Type::kCloseCurly) + { + tokenizer.next(); + return obj; + } + while (!done) + { + Token key_token = tokenizer.next(); + if (key_token.type != Token::Type::kString) throw JsonParseError("unexpected token; expected string (for key)"); + String key_string = parse_string(key_token).get(); + Token colon = tokenizer.next(); + if (colon.type != Token::Type::kColon) throw JsonParseError("unexpected token; expected colon (after key)"); + JsonValue value = parse_value(tokenizer, depth + 1); + Token last = tokenizer.next(); + if (last.type == Token::Type::kCloseCurly) done = true; + else if (last.type != Token::Type::kComma) throw JsonParseError("unexpected token; expected comma (after value)"); + + obj.insert_or_assign(std::move(key_string), std::move(value)); + } + return obj; +} + +static JsonArray parse_array(Tokenizer& tokenizer, int depth) +{ + JsonArray arr; + bool done = false; + if (tokenizer.peek().type == Token::Type::kCloseSquare) + { + tokenizer.next(); + return arr; + } + while (!done) + { + JsonValue value = parse_value(tokenizer, depth + 1); + Token last = tokenizer.next(); + if (last.type == Token::Type::kCloseSquare) done = true; + else if (last.type != Token::Type::kComma) throw JsonParseError("unexpected token; expected comma (after value)"); + arr.push_back(value); + } + return arr; +} + +constexpr const int kMaxDepth = 1000; + +static JsonValue parse_value(Tokenizer& tokenizer, int depth) +{ + using Type = Token::Type; + JsonValue ret; + Token token = tokenizer.next(); + + if (depth >= kMaxDepth) + { + throw JsonParseError("parse depth limit exceeded"); + } + + switch (token.type) + { + case Type::kNull: + ret = JsonValue(); + break; + case Type::kBoolean: + if (token.slice == "true") ret = JsonValue(true); + else if (token.slice == "false") ret = JsonValue(false); + else throw JsonParseError("illegal boolean token"); + break; + case Type::kNumber: + ret = parse_number(token); + break; + case Type::kString: + ret = parse_string(token); + break; + case Type::kOpenCurly: + ret = parse_object(tokenizer, depth); + break; + case Type::kOpenSquare: + ret = parse_array(tokenizer, depth); + break; + case Type::kEof: + throw JsonParseError("reached EOF before parsing value"); + default: + throw JsonParseError("unexpected token"); + } + + return ret; +} + +JsonValue JsonValue::from_json_string(const String& str) +{ + JsonValue ret; + Tokenizer tokenizer { str }; + ret = parse_value(tokenizer, 0); + + Token peek = tokenizer.peek(); + if (peek.type != Token::Type::kEof) throw JsonParseError("unexpected token after expression"); + return ret; +} + +JsonValue::JsonValue(bool value) : type_(Type::kBoolean), boolean_(value) {} +JsonValue::JsonValue(float value) : type_(Type::kNumber), number_((float)value) {} +JsonValue::JsonValue(double value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(int8_t value) : type_(Type::kNumber), number_((int8_t)value) {} +JsonValue::JsonValue(int16_t value) : type_(Type::kNumber), number_((int16_t)value) {} +JsonValue::JsonValue(int32_t value) : type_(Type::kNumber), number_((int32_t)value) {} +JsonValue::JsonValue(int64_t value) : type_(Type::kNumber), number_((int64_t)value) {} +JsonValue::JsonValue(uint8_t value) : type_(Type::kNumber), number_((uint8_t)value) {} +JsonValue::JsonValue(uint16_t value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(uint32_t value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(uint64_t value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(const String& value) : type_(Type::kString), string_(value) {} +JsonValue::JsonValue(String&& value) : type_(Type::kString), string_(std::move(value)) {} +JsonValue::JsonValue(const JsonArray& value) : type_(Type::kArray), array_(value) {} +JsonValue::JsonValue(JsonArray&& value) : type_(Type::kArray), array_(std::move(value)) {} +JsonValue::JsonValue(const JsonObject& value) : type_(Type::kObject), object_(value) {} +JsonValue::JsonValue(JsonObject&& value) : type_(Type::kObject), object_(std::move(value)) {} + +bool JsonValue::operator==(const JsonValue& rhs) const +{ + if (type_ != rhs.type_) + { + return false; + } + switch (type_) + { + case Type::kNull: + return true; + case Type::kBoolean: + return boolean_ == rhs.boolean_; + case Type::kNumber: + return number_ == rhs.number_; + case Type::kString: + return string_ == rhs.string_; + case Type::kArray: + return array_ == rhs.array_; + case Type::kObject: + return object_ == rhs.object_; + } + return false; +} + +void srb2::to_json(JsonValue& to, bool value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int8_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int16_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int32_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int64_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint8_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint16_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint32_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint64_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, float value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, double value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, const String& value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, const JsonArray& value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, const JsonObject& value) { to = JsonValue(value); } + +void srb2::to_json(JsonValue& to, const std::string& v) +{ + to = JsonValue(static_cast(v)); +} + +void srb2::from_json(const JsonValue& from, bool& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int8_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int16_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int32_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int64_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint8_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint16_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint32_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint64_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, float& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, double& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, String& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, JsonArray& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, JsonObject& value) { value = from.get(); } + +void srb2::from_json(const JsonValue& from, std::string& to) +{ + to = static_cast(from.get()); +} + +template class srb2::Vector; +template class srb2::HashMap; diff --git a/src/core/json.hpp b/src/core/json.hpp new file mode 100644 index 000000000..6492c50bb --- /dev/null +++ b/src/core/json.hpp @@ -0,0 +1,533 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_JSON_HPP +#define SRB2_CORE_JSON_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hash_map.hpp" +#include "string.h" +#include "vector.hpp" + +#include "json_macro.inl" + +namespace srb2 +{ + +class JsonValue; + +using JsonArray = Vector; +using JsonObject = HashMap; + +class JsonError : public std::runtime_error +{ +public: + JsonError(const std::string& what); + JsonError(const char* what); + JsonError(const JsonError&) noexcept; +}; + +class JsonParseError : public JsonError +{ +public: + JsonParseError(const std::string& what); + JsonParseError(const char* what); + JsonParseError(const JsonParseError&) noexcept; +}; + +class JsonValue +{ +public: + enum class Type : int + { + kNull, + kBoolean, + kNumber, + kString, + kArray, + kObject + }; + +private: + Type type_; + union { + struct {} dummy; + bool boolean_; + double number_; + String string_; + JsonArray array_; + JsonObject object_; + }; + + static void value_to_ubjson(srb2::Vector& out); + static void value_to_ubjson(srb2::Vector& out, bool value); + static void value_to_ubjson(srb2::Vector& out, double value); + static void value_to_ubjson(srb2::Vector& out, uint64_t value); + static void value_to_ubjson(srb2::Vector& out, const String& value, bool include_type); + static void value_to_ubjson(srb2::Vector& out, const JsonArray& value); + static void value_to_ubjson(srb2::Vector& out, const JsonObject& value); + + void do_to_ubjson(srb2::Vector& out) const; + +public: + JsonValue(); + JsonValue(const JsonValue&); + JsonValue(JsonValue&&) noexcept; + ~JsonValue(); + JsonValue& operator=(const JsonValue&); + JsonValue& operator=(JsonValue&&) noexcept; + + JsonValue(const String& value); + JsonValue(String&& value); + JsonValue(const JsonArray& value); + JsonValue(JsonArray&& value); + JsonValue(const JsonObject& value); + JsonValue(JsonObject&& value); + JsonValue(float value); + JsonValue(double value); + JsonValue(int8_t value); + JsonValue(int16_t value); + JsonValue(int32_t value); + JsonValue(int64_t value); + JsonValue(uint8_t value); + JsonValue(uint16_t value); + JsonValue(uint32_t value); + JsonValue(uint64_t value); + JsonValue(bool value); + + static JsonValue array() { return JsonValue(JsonArray()); } + static JsonValue object() { return JsonValue(JsonObject()); } + + bool operator==(const JsonValue& rhs) const; + bool operator!=(const JsonValue& rhs) const { return !(*this == rhs); } + + void to_json(JsonValue& to) const; + void from_json(const JsonValue& from); + String to_json_string() const; + static JsonValue from_json_string(const String& str); + srb2::Vector to_ubjson() const; + static JsonValue from_ubjson(tcb::span ubjson); + constexpr Type type() const noexcept { return type_; } + + template V get() const; + + constexpr bool is_null() const noexcept { return type_ == Type::kNull; } + constexpr bool is_boolean() const noexcept { return type_ == Type::kBoolean; } + constexpr bool is_number() const noexcept { return type_ == Type::kNumber; } + constexpr bool is_string() const noexcept { return type_ == Type::kString; } + constexpr bool is_array() const noexcept { return type_ == Type::kArray; } + constexpr bool is_object() const noexcept { return type_ == Type::kObject; } + JsonArray& as_array(); + JsonObject& as_object(); + const JsonArray& as_array() const; + const JsonObject& as_object() const; + + JsonValue& at(uint32_t i); + const JsonValue& at(uint32_t i) const; + JsonValue& at(const char* key); + JsonValue& at(const String& key); + const JsonValue& at(const char* key) const; + const JsonValue& at(const String& key) const; + + template + V value(const char* key, const V& def) const; + template + V value(const String& key, const V& def) const; + template + V value(const char* key, V&& def) const; + template + V value(const String& key, V&& def) const; + + bool contains(const char* key) const; + bool contains(const String& key) const; + + JsonValue& operator[](uint32_t i); + JsonValue& operator[](const char* key); + JsonValue& operator[](const String& key); +}; + +void to_json(JsonValue& to, bool value); +void to_json(JsonValue& to, int8_t value); +void to_json(JsonValue& to, int16_t value); +void to_json(JsonValue& to, int32_t value); +void to_json(JsonValue& to, int64_t value); +void to_json(JsonValue& to, uint8_t value); +void to_json(JsonValue& to, uint16_t value); +void to_json(JsonValue& to, uint32_t value); +void to_json(JsonValue& to, uint64_t value); +void to_json(JsonValue& to, float value); +void to_json(JsonValue& to, double value); +void to_json(JsonValue& to, const String& value); +void to_json(JsonValue& to, const JsonArray& value); +void to_json(JsonValue& to, const JsonObject& value); + +void from_json(const JsonValue& to, bool& value); +void from_json(const JsonValue& to, int8_t& value); +void from_json(const JsonValue& to, int16_t& value); +void from_json(const JsonValue& to, int32_t& value); +void from_json(const JsonValue& to, int64_t& value); +void from_json(const JsonValue& to, uint8_t& value); +void from_json(const JsonValue& to, uint16_t& value); +void from_json(const JsonValue& to, uint32_t& value); +void from_json(const JsonValue& to, uint64_t& value); +void from_json(const JsonValue& to, float& value); +void from_json(const JsonValue& to, double& value); +void from_json(const JsonValue& to, String& value); +void from_json(const JsonValue& to, JsonArray& value); +void from_json(const JsonValue& to, JsonObject& value); + +template +inline void to_json(JsonValue& to, const std::array& v) +{ + JsonArray arr; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + JsonValue conv; + to_json(conv, *itr); + arr.push_back(conv); + } + to = JsonValue(std::move(arr)); +} + +template +inline void from_json(const JsonValue& to, std::array& v) +{ + if (!to.is_array()) + { + throw JsonError("json value must be an array"); + } + const JsonArray& arr = to.as_array(); + size_t si = 0; + for (auto& i : arr) + { + if (si >= v.size()) + { + break; + } + + T conv; + from_json(i, conv); + v[si] = std::move(conv); + si++; + } +} + +template +inline void to_json(JsonValue& to, const std::vector& v) +{ + JsonArray arr; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + JsonValue conv; + to_json(conv, *itr); + arr.push_back(conv); + } + to = JsonValue(std::move(arr)); +} + +template +inline void from_json(const JsonValue& to, std::vector& v) +{ + if (!to.is_array()) + { + throw JsonError("json value must be an array"); + } + v.clear(); + const JsonArray& arr = to.as_array(); + for (auto& i : arr) + { + T conv; + from_json(i, conv); + v.push_back(std::move(conv)); + } +} + +template +inline void to_json(JsonValue& to, const srb2::Vector& v) +{ + JsonArray arr; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + JsonValue conv; + to_json(conv, *itr); + arr.push_back(conv); + } + to = JsonValue(std::move(arr)); +} + +template +inline void from_json(const JsonValue& to, srb2::Vector& v) +{ + if (!to.is_array()) + { + throw JsonError("json value must be an array"); + } + v.clear(); + const JsonArray& arr = to.as_array(); + for (auto& i : arr) + { + T conv; + from_json(i, conv); + v.push_back(std::move(conv)); + } +} + +template +inline void to_json(JsonValue& to, const std::unordered_map& v) +{ + JsonObject obj; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + to_json(obj[itr->first], itr->second); + } + to = JsonValue(std::move(obj)); +} + +template +inline void from_json(const JsonValue& to, std::unordered_map& v) +{ + if (!to.is_object()) + { + throw JsonError("json value must be an object"); + } + v.clear(); + const JsonObject& obj = to.as_object(); + for (auto itr = obj.begin(); itr != obj.end(); itr++) + { + V conv; + from_json(itr->second, conv); + v[itr->first] = std::move(conv); + } +} + +template +inline void to_json(JsonValue& to, const srb2::HashMap& v) +{ + JsonObject obj; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + to_json(obj[itr->first], itr->second); + } + to = JsonValue(std::move(obj)); +} + +template +inline void from_json(const JsonValue& to, srb2::HashMap& v) +{ + if (!to.is_object()) + { + throw JsonError("json value must be an object"); + } + v.clear(); + const JsonObject& obj = to.as_object(); + for (auto itr = obj.begin(); itr != obj.end(); itr++) + { + V conv; + from_json(itr->second, conv); + v[itr->first] = std::move(conv); + } +} + +void from_json(const JsonValue& to, std::string& v); +void to_json(JsonValue& to, const std::string& v); + +// template +// inline void to_json(JsonValue& to, const srb2::OAHashMap& v) +// { +// JsonObject obj; +// for (auto itr = v.begin(); itr != v.end(); itr++) +// { +// to_json(obj[itr->first], itr->second); +// } +// to = JsonValue(std::move(obj)); +// } + +// template +// inline void from_json(const JsonValue& to, srb2::OAHashMap& v) +// { +// if (!to.is_object()) +// { +// throw JsonError("json value must be an object"); +// } +// v.clear(); +// const JsonObject& obj = to.as_object(); +// for (auto itr = obj.begin(); itr != obj.end(); itr++) +// { +// V conv; +// from_json(itr->second, conv); +// v[itr->first] = std::move(conv); +// } +// } +// + +template +inline V JsonValue::get() const +{ + V v; + from_json(*this, v); + return v; +} + +template +inline V JsonValue::value(const char* key, const V& def) const +{ + V copy { def }; + return value(key, std::move(copy)); +} + +template +inline V JsonValue::value(const String& key, const V& def) const +{ + return value(key.c_str(), def); +} + +template +inline V JsonValue::value(const char* key, V&& def) const +{ + const JsonObject& obj = as_object(); + auto itr = obj.find(key); + if (itr != obj.end()) + { + return itr->second.get(); + } + return def; +} + +template <> bool JsonValue::get() const; +template <> int8_t JsonValue::get() const; +template <> int16_t JsonValue::get() const; +template <> int32_t JsonValue::get() const; +template <> int64_t JsonValue::get() const; +template <> uint8_t JsonValue::get() const; +template <> uint16_t JsonValue::get() const; +template <> uint32_t JsonValue::get() const; +template <> uint64_t JsonValue::get() const; +template <> float JsonValue::get() const; +template <> double JsonValue::get() const; +template <> String JsonValue::get() const; +template <> std::string JsonValue::get() const; +template <> std::string_view JsonValue::get() const; +template <> JsonArray JsonValue::get() const; +template <> JsonObject JsonValue::get() const; +template <> JsonValue JsonValue::get() const; + +inline bool operator==(const JsonValue& lhs, bool rhs) +{ + if (!lhs.is_boolean()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, int64_t rhs) +{ + if (!lhs.is_number()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, uint64_t rhs) +{ + if (!lhs.is_number()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, double rhs) +{ + if (!lhs.is_number()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, std::string_view rhs) +{ + if (!lhs.is_string()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, const JsonArray& rhs) +{ + if (!lhs.is_array()) + { + return false; + } + return lhs.as_array() == rhs; +} + +inline bool operator==(const JsonValue& lhs, const JsonObject& rhs) +{ + if (!lhs.is_object()) + { + return false; + } + return lhs.as_object() == rhs; +} + +inline bool operator!=(const JsonValue& lhs, bool rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, int64_t rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, uint64_t rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, double rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, std::string_view rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, const JsonArray& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, const JsonObject& rhs) +{ + return !(lhs == rhs); +} + +extern template class Vector; +extern template class HashMap; + +} // namespace srb2 + +#endif // SRB2_CORE_JSON_HPP diff --git a/src/core/json_macro.inl b/src/core/json_macro.inl new file mode 100644 index 000000000..ce8b01db5 --- /dev/null +++ b/src/core/json_macro.inl @@ -0,0 +1,185 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +// file: macro_scope.hpp + +// This file is derived from nlohmman json's codegen macros and thus is provided under its license. + +#define SRB2_JSON_EXPAND( x ) x +#define SRB2_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define SRB2_JSON_PASTE(...) SRB2_JSON_EXPAND(SRB2_JSON_GET_MACRO(__VA_ARGS__, \ + SRB2_JSON_PASTE64, \ + SRB2_JSON_PASTE63, \ + SRB2_JSON_PASTE62, \ + SRB2_JSON_PASTE61, \ + SRB2_JSON_PASTE60, \ + SRB2_JSON_PASTE59, \ + SRB2_JSON_PASTE58, \ + SRB2_JSON_PASTE57, \ + SRB2_JSON_PASTE56, \ + SRB2_JSON_PASTE55, \ + SRB2_JSON_PASTE54, \ + SRB2_JSON_PASTE53, \ + SRB2_JSON_PASTE52, \ + SRB2_JSON_PASTE51, \ + SRB2_JSON_PASTE50, \ + SRB2_JSON_PASTE49, \ + SRB2_JSON_PASTE48, \ + SRB2_JSON_PASTE47, \ + SRB2_JSON_PASTE46, \ + SRB2_JSON_PASTE45, \ + SRB2_JSON_PASTE44, \ + SRB2_JSON_PASTE43, \ + SRB2_JSON_PASTE42, \ + SRB2_JSON_PASTE41, \ + SRB2_JSON_PASTE40, \ + SRB2_JSON_PASTE39, \ + SRB2_JSON_PASTE38, \ + SRB2_JSON_PASTE37, \ + SRB2_JSON_PASTE36, \ + SRB2_JSON_PASTE35, \ + SRB2_JSON_PASTE34, \ + SRB2_JSON_PASTE33, \ + SRB2_JSON_PASTE32, \ + SRB2_JSON_PASTE31, \ + SRB2_JSON_PASTE30, \ + SRB2_JSON_PASTE29, \ + SRB2_JSON_PASTE28, \ + SRB2_JSON_PASTE27, \ + SRB2_JSON_PASTE26, \ + SRB2_JSON_PASTE25, \ + SRB2_JSON_PASTE24, \ + SRB2_JSON_PASTE23, \ + SRB2_JSON_PASTE22, \ + SRB2_JSON_PASTE21, \ + SRB2_JSON_PASTE20, \ + SRB2_JSON_PASTE19, \ + SRB2_JSON_PASTE18, \ + SRB2_JSON_PASTE17, \ + SRB2_JSON_PASTE16, \ + SRB2_JSON_PASTE15, \ + SRB2_JSON_PASTE14, \ + SRB2_JSON_PASTE13, \ + SRB2_JSON_PASTE12, \ + SRB2_JSON_PASTE11, \ + SRB2_JSON_PASTE10, \ + SRB2_JSON_PASTE9, \ + SRB2_JSON_PASTE8, \ + SRB2_JSON_PASTE7, \ + SRB2_JSON_PASTE6, \ + SRB2_JSON_PASTE5, \ + SRB2_JSON_PASTE4, \ + SRB2_JSON_PASTE3, \ + SRB2_JSON_PASTE2, \ + SRB2_JSON_PASTE1)(__VA_ARGS__)) +#define SRB2_JSON_PASTE2(func, v1) func(v1) +#define SRB2_JSON_PASTE3(func, v1, v2) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE2(func, v2) +#define SRB2_JSON_PASTE4(func, v1, v2, v3) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE3(func, v2, v3) +#define SRB2_JSON_PASTE5(func, v1, v2, v3, v4) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE4(func, v2, v3, v4) +#define SRB2_JSON_PASTE6(func, v1, v2, v3, v4, v5) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE5(func, v2, v3, v4, v5) +#define SRB2_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define SRB2_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define SRB2_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define SRB2_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define SRB2_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define SRB2_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define SRB2_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define SRB2_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define SRB2_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define SRB2_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define SRB2_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define SRB2_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define SRB2_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define SRB2_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define SRB2_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define SRB2_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define SRB2_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define SRB2_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define SRB2_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define SRB2_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define SRB2_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define SRB2_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define SRB2_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define SRB2_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define SRB2_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define SRB2_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define SRB2_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define SRB2_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define SRB2_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define SRB2_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define SRB2_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define SRB2_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define SRB2_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define SRB2_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define SRB2_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define SRB2_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define SRB2_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define SRB2_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define SRB2_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define SRB2_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define SRB2_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define SRB2_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define SRB2_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define SRB2_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define SRB2_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define SRB2_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define SRB2_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define SRB2_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define SRB2_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define SRB2_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define SRB2_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define SRB2_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define SRB2_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define SRB2_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define SRB2_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define SRB2_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define SRB2_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define SRB2_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define SRB2_JSON_TO(v1) to_json(srb2_json_j.as_object()[#v1], srb2_json_t.v1); +#define SRB2_JSON_FROM(v1) srb2_json_t.v1 = srb2_json_j.as_object().at(#v1); +#define SRB2_JSON_FROM_WITH_DEFAULT(v1) if (srb2_json_j.as_object().find(#v1) != srb2_json_j.as_object().end()) \ + { \ + from_json(srb2_json_j.as_object().find(#v1)->second, srb2_json_t.v1); \ + } \ + else \ + { \ + srb2_json_t.v1 = srb2_json_default_obj.v1; \ + } + +/*! +@brief macro +@def SRB2_JSON_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define SRB2_JSON_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { srb2_json_j = srb2::JsonObject(); SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM, __VA_ARGS__)) } + +#define SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { srb2_json_j = srb2::JsonObject(); SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { const Type srb2_json_default_obj{}; SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +*/ +#define SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { const Type srb2_json_default_obj{}; SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } diff --git a/src/core/string.cpp b/src/core/string.cpp new file mode 100644 index 000000000..2bd0bd24f --- /dev/null +++ b/src/core/string.cpp @@ -0,0 +1,1125 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "string.h" +#include "fmt/format.h" + +#include +#include +#include +#include +#include + +namespace srb2 +{ + +String::String(const String&) = default; +String::String(String&&) noexcept = default; +String::~String() = default; +String& String::operator=(const String&) = default; +String& String::operator=(String&&) noexcept = default; + +String::String(const char* rhs) : String(std::string_view { rhs }) +{} + +String::String(const char* rhs, size_t len) : String(std::string_view { rhs, len }) +{} + +String::String(const std::string& rhs) : String(std::string_view { rhs }) +{} + +String::String(std::string_view view) : String() +{ + append(view); +} + +String::operator std::string() const +{ + std::string_view view = *this; + return std::string(view); +} + +String::operator std::string_view() const +{ + return std::string_view((const char*)data(), size()); +} + +uint32_t String::size() const noexcept +{ + if (data_.empty()) + { + return 0; + } + return data_.size() - 1; +} + +static const char* kEmptyString = ""; + +const char* String::c_str() const +{ + if (data_.empty()) + { + return kEmptyString; + } + return reinterpret_cast(data_.data()); +} + +void String::reserve(size_type capacity) +{ + if (capacity == 0) + { + data_.reserve(0); + return; + } + data_.reserve(capacity + 1); +} + +uint8_t* String::begin() noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data(); +} + +uint8_t* String::end() noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data() + size(); +} + +const uint8_t* String::cbegin() const noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data(); +} + +const uint8_t* String::cend() const noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data() + size(); +} + +uint8_t& String::at(size_type i) +{ + if (i >= size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + return data_.at(i); +} + +const uint8_t& String::at(size_type i) const +{ + if (i >= size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + return data_.at(i); +} + +String& String::insert(size_type index, size_type count, uint8_t ch) +{ + if (index > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + data_.insert(data_.begin() + index, count, ch); + return *this; +} + +String& String::insert(size_type index, const char* s) +{ + return insert(index, s, (size_type)std::strlen(s)); +} + +String& String::insert(size_type index, const char* s, size_type count) +{ + if (index > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + if (!empty()) + { + // remove null byte + data_.pop_back(); + } + data_.insert(data_.begin() + index, s, s + count); + if (data_.size() > 0) + { + data_.push_back(0); + } + return *this; +} + +String& String::insert(size_type index, std::string_view str) +{ + return insert(index, str.begin(), (size_type)str.size()); +} + +String& String::insert(size_type index, std::string_view str, size_t s_index, size_t count) +{ + if (s_index > str.size()) + { + throw std::out_of_range("s_index > str.size()"); + } + return insert(index, str.substr(s_index, std::max(str.size() - s_index, count))); +} + +String::iterator String::insert(const_iterator pos, uint8_t ch) +{ + if (pos < cbegin() || pos > cend()) + { + throw std::out_of_range("insert iterator out of bounds"); + } + return data_.insert(pos, ch); +} + +String::iterator String::insert(const_iterator pos, size_type count, uint8_t ch) +{ + if (pos < cbegin() || pos > cend()) + { + throw std::out_of_range("insert iterator out of bounds"); + } + if (!empty()) + { + data_.pop_back(); + } + for (size_type i = 0; i < count; i++) + { + data_.insert(pos, ch); + } + if (data_.size() > 0) + { + data_.push_back(0); + } + return const_cast(pos); +} + +String& String::erase(size_type index, size_type count) +{ + if (index + count >= size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + const_iterator first = begin() + index; + const_iterator last = first + count; + data_.erase(first, last); + if (data_.size() == 1) + { + data_.pop_back(); + } + return *this; +} + +String::iterator String::erase(const_iterator position) +{ + return data_.erase(position); +} + +String::iterator String::erase(const_iterator first, const_iterator last) +{ + return data_.erase(first, last); +} + +void String::push_back(uint8_t v) +{ + if (data_.empty()) + { + data_.push_back(v); + data_.push_back(0); + return; + } + data_[data_.size() - 1] = v; + data_.push_back(0); +} + +void String::pop_back() +{ + data_.pop_back(); + if (data_.size() == 1) + { + data_.pop_back(); + } + else + { + data_[data_.size() - 1] = 0; + } +} + +String& String::append(size_type count, uint8_t ch) +{ + if (count == 0) + { + return *this; + } + + if (!data_.empty()) + { + data_.pop_back(); + } + + for (size_type i = 0; i < count; i++) + { + data_.push_back(ch); + } + data_.push_back(0); + return *this; +} + +String& String::append(const char* s, size_type count) +{ + insert(size(), s, count); + return *this; +} + +String& String::append(const char* s) +{ + insert(size(), s); + return *this; +} + +String& String::append(std::string_view str) +{ + insert(size(), str); + return *this; +} + +String& String::append(std::string_view str, size_type pos, size_type count) +{ + insert(size(), str, pos, count); + return *this; +} + +String& String::operator+=(std::string_view r) +{ + insert(size(), r); + return *this; +} + +String& String::operator+=(const char* r) +{ + insert(size(), r); + return *this; +} + +String& String::operator+=(uint8_t r) +{ + push_back(r); + return *this; +} + +String& String::operator+=(std::initializer_list r) +{ + append(r.begin(), r.end()); + return *this; +} + +String& String::replace(size_type pos, size_type count, std::string_view str) +{ + return replace(pos, count, str, 0, str.size()); +} + +String& String::replace(const_iterator first, const_iterator last, std::string_view str) +{ + if (first < begin() || last > end() || first + str.size() > end()) + { + throw std::out_of_range("string replacement range out of bounds"); + } + size_type index = first - data_.data(); + size_type count = last - first; + + return replace(index, count, str); +} + +String& String::replace(size_type pos, size_type count, std::string_view str, size_t pos2, size_t count2) +{ + if (pos >= size()) + { + throw std::out_of_range("string replacement range out of bounds"); + } + if (pos2 >= str.size()) + { + throw std::out_of_range("string replacement string_view range out of bounds"); + } + erase(pos, count); + insert(pos, str, pos2, count2); + + return *this; +} + +String& String::replace(size_type pos, size_type count, const char* cstr, size_type count2) +{ + size_t len = std::strlen(cstr); + return replace(pos, count, std::string_view(cstr, len), count2); +} + +String& String::replace(const_iterator first, const_iterator last, const char* cstr, size_type count2) +{ + size_type index = first - data_.data(); + size_type count = last - first; + + return replace(index, count, cstr, count2); +} + +String& String::replace(size_type pos, size_type count, const char* cstr) +{ + size_t len = std::strlen(cstr); + return replace(pos, count, std::string_view(cstr, len)); +} + +String& String::replace(const_iterator first, const_iterator last, const char* cstr) +{ + size_type index = first - data_.data(); + size_type count = last - first; + + return replace(index, count, cstr); +} + +String& String::replace(const_iterator first, const_iterator last, uint8_t ch) +{ + if (first < begin() || last > end()) + { + throw std::out_of_range("string iterators out of range"); + } + for (; first != last; first++) + { + *const_cast(first) = ch; + } + return *this; +} + +String& String::replace(const_iterator first, const_iterator last, std::initializer_list ilist) +{ + return replace(first, last, ilist.begin(), ilist.end()); +} + +String::size_type String::copy(uint8_t* dest, size_type count, size_type pos) const +{ + if (pos > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + size_type copied = 0; + for (size_type i = 0; i < count && (i + pos) < size(); i++) + { + dest[i] = data_[i + pos]; + copied += 1; + } + return copied; +} + +String::size_type String::copy(char* dest, size_type count, size_type pos) const +{ + if (pos > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + size_type copied = 0; + for (size_type i = 0; i < count && (i + pos) < size(); i++) + { + dest[i] = data_[i + pos]; + copied += 1; + } + return copied; +} + +void String::resize(size_type count) +{ + if (count == 0) + { + data_.clear(); + return; + } + data_.resize(count + 1); + data_[count] = 0; +} + +void String::resize(size_type count, uint8_t ch) +{ + if (count == 0) + { + data_.clear(); + return; + } + data_.resize(count + 1, ch); + data_[count] = 0; +} + +void String::swap(String& other) noexcept +{ + std::swap(this->data_, other.data_); +} + +String::size_type String::find(const String& str, size_type pos) const +{ + return find(static_cast(str), pos); +} + +String::size_type String::find(std::string_view str, size_type pos) const +{ + if (size() == 0) + { + return npos; + } + + for (size_type i = pos; i < size(); i++) + { + bool found = true; + for (size_t j = 0; j < str.size() && found; j++) + { + if (i + j >= size() || data_[i + j] != str[j]) + { + found = false; + } + } + if (found) + { + return i; + } + } + return npos; +} + +String::size_type String::find(const char* s, size_type pos, size_t count) const +{ + return find(std::string_view(s, count), pos); +} + +String::size_type String::find(const char* s, size_type pos) const +{ + size_t len = std::strlen(s); + return find(std::string_view(s, len), pos); +} + +String::size_type String::find(uint8_t ch, size_type pos) const +{ + for (size_type i = pos; i < size(); i++) + { + if (data_[i] == ch) + { + return i; + } + } + return npos; +} + +String::size_type String::rfind(const String& str, size_type pos) const +{ + return rfind(static_cast(str), pos); +} + +String::size_type String::rfind(std::string_view str, size_type pos) const +{ + if (str.empty()) + { + return std::min(pos, size()); + } + if (size() == 0) + { + return npos; + } + + for (size_type i = std::min(pos, size()); i >= 0; i--) + { + bool found = true; + for (size_t j = 0; j < str.size() && found; j++) + { + if (i + j >= size() || data_[i + j] != str[j]) + { + found = false; + } + } + if (found) + { + return i; + } + } + return npos; +} + +String::size_type String::rfind(const char* s, size_type pos, size_type count) const +{ + return rfind(std::string_view(s, count), pos); +} + +String::size_type String::rfind(const char* s, size_type pos) const +{ + size_t len = std::strlen(s); + return rfind(std::string_view(s, len), pos); +} + +String::size_type String::rfind(uint8_t ch, size_type pos) const +{ + if (empty()) + { + return npos; + } + + for (size_type i = std::min(pos, size()); i >= 0; i--) + { + if (data_[i] == ch) + { + return i; + } + } + + return npos; +} + +int String::compare(std::string_view str) const noexcept +{ + std::string_view self = *this; + return self.compare(str); +} + +int String::compare(const char* s) const +{ + std::string_view self = *this; + std::string_view that { s, std::strlen(s) }; + return self.compare(that); +} + +String String::substr(size_type pos, size_type count) const +{ + String ret; + if (pos >= size()) + { + throw std::out_of_range("string byte index invalid"); + } + size_type start = pos; + size_type end = std::min(pos + count, size() - pos); + ret.reserve(end - start); + for (size_type i = start; i < end; i++) + { + ret.push_back(data_[i]); + } + return ret; +} + +bool String::valid_utf8() const noexcept +{ + for (auto itr = decode_begin(); itr != decode_end(); itr++) + { + if (!itr.valid()) + { + return false; + } + } + return true; +} + +Vector String::to_utf16() const +{ + return ::srb2::to_utf16(static_cast(*this)); +} + +Vector to_utf16(std::string_view utf8) +{ + Vector ret; + for (auto itr = Utf8Iter::begin(utf8); itr != Utf8Iter::end(utf8); itr++) + { + uint32_t codepoint = *itr; + if (codepoint < 0x10000) + { + ret.push_back(static_cast(codepoint)); + continue; + } + // high surrogate + ret.push_back(static_cast( + (((codepoint - 0x10000) & 0b11111111110000000000) >> 10) + 0xD800 + )); + // low surrogate + ret.push_back(static_cast( + (((codepoint - 0x10000) & 0b1111111111)) + 0xDC00 + )); + } + return ret; +} + +Vector to_utf32(std::string_view utf8) +{ + Vector ret; + for (auto itr = Utf8Iter::begin(utf8); itr != Utf8Iter::end(utf8); itr++) + { + ret.push_back(itr.codepoint()); + } + return ret; +} + +StaticVec to_utf8(uint32_t codepoint) +{ + StaticVec enc; + if (codepoint < 0x80) + { + enc.push_back(static_cast(codepoint)); + } + else if (codepoint >= 0x80 && codepoint < 0x800) + { + enc.push_back(((codepoint >> 6) & 0b11111) + 0xC0); + enc.push_back((codepoint & 0b111111) + 0x80); + } + else if (codepoint >= 0x800 && codepoint < 0x10000) + { + enc.push_back(((codepoint >> 12) & 0b1111) + 0xE0); + enc.push_back(((codepoint >> 6) & 0b111111) + 0x80); + enc.push_back((codepoint & 0b111111) + 0x80); + } + else if (codepoint >= 0x10000 && codepoint < 0x110000) + { + enc.push_back(((codepoint >> 18) & 0b111) + 0xF0); + enc.push_back(((codepoint >> 12) & 0b111111) + 0x80); + enc.push_back(((codepoint >> 6) & 0b111111) + 0x80); + enc.push_back((codepoint & 0b111111) + 0x80); + } + else + { + // replacement char due to invalid codepoint + enc = to_utf8(0xFFFD); + } + return enc; +} + +String to_utf8(std::u32string_view utf32view) +{ + return to_utf8(utf32view.begin(), utf32view.end()); +} + +String operator+(const String& lhs, const String& rhs) +{ + String ret; + ret.append(lhs); + ret.append(rhs); + return ret; +} + +String operator+(const String& lhs, const char* rhs) +{ + String ret; + size_t len = std::strlen(rhs); + ret.append(lhs); + ret.append(std::string_view(rhs, len)); + return ret; +} + +String operator+(const String& lhs, uint8_t rhs) +{ + String ret; + ret.append(lhs); + ret.push_back(rhs); + return ret; +} + +String operator+(const String& lhs, std::string_view view) +{ + String ret; + ret.append(lhs); + ret.append(view); + return ret; +} + +bool operator==(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) == 0; +} + +bool operator==(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) == 0; +} + +// bool operator==(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) == 0; +// } + +bool operator!=(const String& lhs, const String& rhs) +{ + return !(lhs == rhs); +} + +bool operator!=(const String& lhs, const char* rhs) +{ + return !(lhs == rhs); +} + +// bool operator!=(const String& lhs, std::string_view rhs) +// { +// return !(lhs == rhs); +// } + +bool operator<(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) < 0; +} + +bool operator<(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) < 0; +} + +// bool operator<(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) < 0; +// } + +bool operator<=(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) <= 0; +} + +bool operator<=(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) <= 0; +} + +// bool operator<=(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) <= 0; +// } + +bool operator>(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) > 0; +} + +bool operator>(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) > 0; +} + +// bool operator>(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) > 0; +// } + +bool operator>=(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) >= 0; +} + +bool operator>=(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) >= 0; +} + +// bool operator>=(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) >= 0; +// } + +static constexpr bool is_utf8_byte(uint8_t b) +{ + return b != 0xC0 && b != 0xC1 && b < 0xF5; +} + +static constexpr bool is_utf8_continuation(uint8_t b) +{ + return b >= 0x80 && b < 0xC0; +} + +uint32_t Utf8Iter::do_codepoint() const +{ + uint8_t b[4]; + uint8_t s; + bool v = true; + b[0] = s_[i_]; + if (b[0] < 0x80) s = 1; + else if (b[0] >= 0x80 && b[0] < 0xC0) + { + // invalid, first byte continuation + s = 1; + v = false; + } + else if (b[0] >= 0xC0 && b[0] < 0xE0) + { + // 2 byte + if (s_.size() - i_ < 2) + { + // invalid, truncated + s = 1; + v = false; + goto decode; + } + + b[1] = s_[i_ + 1]; + + if (!is_utf8_continuation(b[1])) + { + // invalid, not a continuation + s = 1; + v = false; + goto decode; + } + + s = 2; + } + else if (b[0] >= 0xE0 && b[0] < 0xF0) + { + // 3 byte + if (s_.size() - i_ < 2) + { + // invalid, truncated + s = 1; + v = false; + goto decode; + } + if (s_.size() - i_ < 3) + { + // invalid, truncated + s = 2; + v = false; + goto decode; + } + + b[1] = s_[i_ + 1]; + b[2] = s_[i_ + 2]; + + if (!is_utf8_continuation(b[1])) + { + // invalid, not a continuation + s = 1; + v = false; + goto decode; + } + + if (!is_utf8_continuation(b[2])) + { + // invalid, not a continuation + s = 2; + v = false; + goto decode; + } + + s = 3; + } + else if (b[0] >= 0xF0 && b[0] < 0xF5) + { + // 4 byte + if (s_.size() - i_ < 2) + { + // invalid, truncated + s = 1; + v = false; + goto decode; + } + if (s_.size() - i_ < 3) + { + // invalid, truncated + s = 2; + v = false; + goto decode; + } + if (s_.size() - i_ < 4) + { + // invalid, truncated + s = 3; + v = false; + goto decode; + } + + b[1] = s_[i_ + 1]; + b[2] = s_[i_ + 2]; + b[3] = s_[i_ + 3]; + + if (!is_utf8_continuation(b[1])) + { + // invalid, not a continuation + s = 1; + v = false; + goto decode; + } + + if (!is_utf8_continuation(b[2])) + { + // invalid, not a continuation + s = 2; + v = false; + goto decode; + } + + if (!is_utf8_continuation(b[3])) + { + // invalid, not a continuation + s = 3; + v = false; + goto decode; + } + + s = 4; + } + else + { + // invalid + s = 1; + v = false; + } + +decode: + // bit 29 indicates unparseable (immediately invalid, replacement char U+FFFD) + // bit 30-31 indicates byte size (0-3) + if (v == false) return 0xFFFD + ((s - 1) << 30) + (1 << 29); + + switch (s) + { + default: + case 1: return b[0] & 0x7f; + case 2: return (b[1] & 0x3f) + ((b[0] & 0x1f) << 6) + (1 << 30); + case 3: return (b[2] & 0x3f) + ((b[1] & 0x3f) << 6) + ((b[0] & 0x0f) << 12) + (2 << 30); + case 4: return (b[3] & 0x3f) + ((b[2] & 0x3f) << 6) + ((b[1] & 0x3f) << 12) + ((b[2] & 0x7) << 18) + (3 << 30); + } +} + +uint32_t Utf8Iter::codepoint() const +{ + uint32_t c = do_codepoint(); + uint32_t ret = c & 0x001fffff; + uint8_t s = c >> 30; + + // overlong encodings are still invalid and should be replaced, + // even if bit 29 is unset + switch (s) + { + default: + case 0: return ret >= (2 << 8) ? 0xFFFD : ret; + case 1: return ret >= (2 << 12) ? 0xFFFD : ret; + case 2: return ret >= (2 << 17) ? 0xFFFD : ret; + case 3: return ret; + } +} + +bool Utf8Iter::valid() const +{ + uint32_t c = do_codepoint(); + uint32_t ret = c & 0x001fffff; + if ((c >> 29) & 1) return false; + uint8_t s = c >> 30; + + switch (s) + { + default: + case 0: return ret >= (2 << 8) ? false : true; + case 1: return ret >= (2 << 12) ? false : true; + case 2: return ret >= (2 << 17) ? false : true; + case 3: return true; + } +} + +uint8_t Utf8Iter::size() const +{ + uint32_t c = do_codepoint(); + uint8_t s = (c >> 30); + return s + 1; +} + +static constexpr bool utf16_is_low_surrogate(uint16_t word) +{ + return word >= 0xDC00 && word < 0xDFFF; +} + +static constexpr bool utf16_is_high_surrogate(uint16_t word) +{ + return word >= 0xD800 && word < 0xDBFF; +} + +static constexpr bool utf16_is_surrogate(uint16_t word) +{ + return utf16_is_high_surrogate(word) || utf16_is_low_surrogate(word); +} + +uint32_t Utf16Iter::do_codepoint() const +{ + uint16_t words[2]; + words[0] = s_[i_]; + if (!utf16_is_high_surrogate(words[0])) + { + // unpaired low surrogates allowed as-is for windows compatibility + return words[0]; + } + if (s_.size() - i_ < 2) + { + // unpaired high surrogates allowed as-is for windows compatibility + return words[0]; + } + words[1] = s_[i_ + 1]; + return ((words[1] - 0xDC00) & 0x3FF) + + ((words[0] - 0xD800) & 0x3FF) + + 0x10000; +} + +uint32_t Utf16Iter::codepoint() const +{ + uint32_t c = do_codepoint(); + uint32_t ret = c & 0x001fffff; + + return ret; +} + +uint8_t Utf16Iter::size() const +{ + uint32_t c = do_codepoint() & 0x001fffff; + return c >= 0x10000 ? 2 : 1; +} + +// fmtlib + +String vformat(fmt::string_view fmt, fmt::format_args args) +{ + auto buf = fmt::memory_buffer(); + vformat_to(buf, fmt, args); + return { buf.data(), buf.size() }; +} + +} // namespace srb2 + +size_t std::hash::operator()(const srb2::String& v) +{ + std::string_view str = v; + return std::hash()(str); +} + +// C functions + +int Str_IsValidUTF8(const char* str) +{ + size_t len = std::strlen(str); + if (len == 0) + { + return 1; + } + + for (auto itr = srb2::Utf8Iter::begin(str); itr != srb2::Utf8Iter::end(str + len - 1); ++itr) + { + if (!itr.valid()) + { + return false; + } + } + return true; +} + +uint32_t Str_NextCodepointFromUTF8(const char** itr) +{ + auto i = srb2::Utf8Iter::begin(*itr); + uint32_t ret = i.codepoint(); + uint8_t s = i.size(); + *itr += s; + return ret; +} diff --git a/src/core/string.h b/src/core/string.h new file mode 100644 index 000000000..f91294b97 --- /dev/null +++ b/src/core/string.h @@ -0,0 +1,453 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_STRING_HPP +#define SRB2_CORE_STRING_HPP + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +#include "fmt/core.h" + +#include "static_vec.hpp" +#include "vector.hpp" + +namespace srb2 +{ + +class Utf8Iter +{ +public: + using difference_type = size_t; + using value_type = uint32_t; + using pointer = void; + using reference = void; + using iterator_category = std::input_iterator_tag; + +private: + size_t i_; + std::string_view s_; + + Utf8Iter(std::string_view s, size_t i) : i_(i), s_(s) {} + + friend class String; + uint32_t do_codepoint() const; + +public: + Utf8Iter() = default; + Utf8Iter(const Utf8Iter&) = default; + Utf8Iter(Utf8Iter&&) noexcept = default; + ~Utf8Iter() = default; + Utf8Iter& operator=(const Utf8Iter&) = default; + Utf8Iter& operator=(Utf8Iter&&) noexcept = default; + + static Utf8Iter begin(std::string_view s) { return Utf8Iter(s, 0); } + static Utf8Iter end(std::string_view s) { return Utf8Iter(s, s.size()); } + + bool operator==(const Utf8Iter& r) const noexcept + { + return s_ == r.s_ && i_ == r.i_; + } + + bool operator!=(const Utf8Iter& r) const noexcept { return !(*this == r); } + + uint32_t operator*() const { return codepoint(); } + Utf8Iter& operator++() + { + i_ += size(); + return *this; + } + Utf8Iter operator++(int) + { + Utf8Iter copy = *this; + ++*this; + return copy; + } + uint32_t codepoint() const; + bool valid() const; + uint8_t size() const; +}; + +class Utf16Iter +{ +public: + using difference_type = size_t; + using value_type = uint32_t; + using pointer = void; + using reference = void; + using iterator_category = std::input_iterator_tag; + +private: + size_t i_; + std::u16string_view s_; + + Utf16Iter(std::u16string_view s, size_t i) : i_(i), s_(s) {} + + uint32_t do_codepoint() const; + +public: + Utf16Iter() = default; + Utf16Iter(const Utf16Iter&) = default; + Utf16Iter(Utf16Iter&&) noexcept = default; + ~Utf16Iter() = default; + Utf16Iter& operator=(const Utf16Iter&) = default; + Utf16Iter& operator=(Utf16Iter&&) = default; + + static Utf16Iter begin(std::u16string_view s) { return Utf16Iter(s, 0); } + static Utf16Iter end(std::u16string_view s) { return Utf16Iter(s, s.size()); } + + bool operator==(const Utf16Iter& r) const noexcept + { + return s_ == r.s_ && i_ == r.i_; + } + + bool operator!=(const Utf16Iter& r) const noexcept { return !(*this == r); } + + uint32_t operator*() const { return codepoint(); } + Utf16Iter& operator++() + { + i_ += size(); + return *this; + } + Utf16Iter operator++(int) + { + Utf16Iter copy = *this; + ++*this; + return copy; + } + + uint32_t codepoint() const; + bool valid() const { return true; } // we allow unpaired surrogates in general + uint8_t size() const; +}; + +class String +{ +public: + using size_type = uint32_t; + using difference_type = int64_t; + using value_type = uint8_t; + using reference = uint8_t&; + using const_reference = const uint8_t&; + using pointer = uint8_t*; + using const_pointer = const uint8_t*; + using iterator = uint8_t*; + using const_iterator = const uint8_t*; + +private: + srb2::Vector data_; + +public: + + friend struct std::hash; + + static constexpr const size_type npos = -1; + + String() = default; + String(const String&); + String(String&&) noexcept; + ~String(); + String& operator=(const String&); + String& operator=(String&&) noexcept; + + String(const char* s); + String(const char* s, size_t len); + String(const std::string&); + + explicit String(std::string_view view); + + operator std::string() const; + operator std::string_view() const; + + size_type size() const noexcept; + bool empty() const noexcept { return data_.empty(); } + const char* c_str() const; + uint8_t* data() noexcept { return data_.data(); } + const uint8_t* data() const noexcept { return data_.data(); } + void reserve(size_type capacity); + + uint8_t* begin() noexcept; + uint8_t* end() noexcept; + const uint8_t* cbegin() const noexcept; + const uint8_t* cend() const noexcept; + const uint8_t* begin() const noexcept { return cbegin(); } + const uint8_t* end() const noexcept { return cend(); } + + uint8_t& at(size_type i); + const uint8_t& at(size_type i) const; + uint8_t& operator[](size_type i) { return data_[i]; } + const uint8_t& operator[](size_type i) const { return data_[i]; } + uint8_t& front() { return data_[0]; } + const uint8_t& front() const { return data_[0]; } + uint8_t& back() { return data_[size() - 1]; } + const uint8_t& back() const { return data_[size() - 1]; } + + void clear() { data_.clear(); } + String& insert(size_type index, size_type count, uint8_t ch); + String& insert(size_type index, const char* s); + String& insert(size_type index, const char* s, size_type count); + String& insert(size_type index, std::string_view str); + String& insert(size_type index, std::string_view str, size_t s_index, size_t count = npos); + iterator insert(const_iterator pos, uint8_t ch); + iterator insert(const_iterator pos, size_type count, uint8_t ch); + + template < + typename InputIt, + typename std::enable_if_t< + std::is_constructible< + uint8_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + iterator insert(const_iterator pos, InputIt first, InputIt last) + { + size_type offset = pos - data(); + if (!empty()) + { + // remove null byte + data_.pop_back(); + } + auto ret = data_.insert(pos, first, last); + if (data_.size() > 0) + { + data_.push_back(0); + } + return data() + offset; + } + + iterator insert(const_iterator pos, std::initializer_list list); + String& erase(size_type index = 0, size_type count = npos); + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + void push_back(uint8_t v); + void pop_back(); + String& append(size_type count, uint8_t ch); + String& append(const char* s, size_type count); + String& append(const char* s); + String& append(std::string_view str); + String& append(std::string_view str, size_type pos, size_type count = npos); + + template < + typename InputIt, + typename std::enable_if_t< + std::is_constructible< + uint8_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + String& append(InputIt first, InputIt last) + { + if (!empty()) + { + // remove null byte + data_.pop_back(); + } + for (; first != last; first++) + { + data_.push_back(*first); + } + if (data_.size() > 0) + { + data_.push_back(0); + } + return *this; + } + + String& append(std::initializer_list ilist); + String& operator+=(std::string_view r); + String& operator+=(const char* r); + String& operator+=(uint8_t r); + String& operator+=(std::initializer_list r); + String& replace(size_type pos, size_type count, std::string_view str); + String& replace(const_iterator first, const_iterator last, std::string_view str); + String& replace(size_type pos, size_type count, std::string_view str, size_t pos2, size_t count2 = -1); + String& replace(size_type pos, size_type count, const char* cstr, size_type count2); + String& replace(const_iterator first, const_iterator last, const char* cstr, size_type count2); + String& replace(size_type pos, size_type count, const char* cstr); + String& replace(const_iterator first, const_iterator last, const char* cstr); + // String& replace(size_type pos, size_type count, size_type count2, uint8_t ch); + String& replace(const_iterator first, const_iterator last, uint8_t ch); + + template < + typename InputIt, + typename std::enable_if_t< + std::is_constructible< + uint8_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + String& replace(const_iterator first, const_iterator last, InputIt first2, InputIt last2) + { + for (; first != last && first2 != last2; first++, first2++) + { + *const_cast(first) = *first2; + } + return *this; + } + + String& replace(const_iterator first, const_iterator last, std::initializer_list ilist); + size_type copy(uint8_t* dest, size_type count, size_type pos = 0) const; + size_type copy(char* dest, size_type count, size_type pos = 0) const; + void resize(size_type count); + void resize(size_type count, uint8_t ch); + void swap(String& other) noexcept; + + size_type find(const String& str, size_type pos = 0) const; + size_type find(std::string_view str, size_type pos = 0) const; + size_type find(const char* s, size_type pos, size_t count) const; + size_type find(const char* s, size_type pos = 0) const; + size_type find(uint8_t ch, size_type pos = 0) const; + size_type rfind(const String& str, size_type pos = npos) const; + size_type rfind(std::string_view str, size_type pos = npos) const; + size_type rfind(const char* s, size_type pos, size_type count) const; + size_type rfind(const char* s, size_type pos = npos) const; + size_type rfind(uint8_t ch, size_type pos = npos) const; + // size_type find_first_of(std::string_view str, size_type pos = 0) const; + // size_type find_first_of(const char* s, size_type pos, size_type count) const; + // size_type find_first_of(const char* s, size_type pos = 0) const; + // size_type find_first_of(uint8_t ch, size_type pos = 0) const; + // size_type find_first_not_of(std::string_view str, size_type pos = 0) const; + // size_type find_first_not_of(const char* s, size_type pos, size_type count) const; + // size_type find_first_not_of(const char* s, size_type pos = 0) const; + // size_type find_first_not_of(uint8_t ch, size_type pos = 0) const; + // size_type find_last_of(std::string_view str, size_type pos = npos) const; + // size_type find_last_of(const char* s, size_type pos, size_type count) const; + // size_type find_last_of(const char* s, size_type pos = npos) const; + // size_type find_last_of(uint8_t ch, size_type pos = npos) const; + // size_type find_last_not_of(std::string_view str, size_type pos = npos) const; + // size_type find_last_not_of(const char* s, size_type pos, size_type count) const; + // size_type find_last_not_of(const char* s, size_type pos = npos) const; + // size_type find_last_not_of(uint8_t ch, size_type pos = npos) const; + + int compare(std::string_view str) const noexcept; + // int compare(size_type pos1, size_type count1, std::string_view str) const; + // int compare(size_type pos1, size_type count1, std::string_view str, size_type pos2, size_type count2 = npos) const; + int compare(const char* s) const; + // int compare(size_type pos1, size_type count1, const char* s) const; + // int compare(size_type pos1, size_type count1, const char* s, size_type count2) const; + String substr(size_type pos = 0, size_type count = npos) const; + + // Non-STL String functions + bool valid_utf8() const noexcept; + srb2::Vector to_utf16() const; + srb2::Vector to_utf32() const; + size_t length_decoded() const; + + Utf8Iter decode_begin() const { return Utf8Iter(*this, 0); } + Utf8Iter decode_end() const { return Utf8Iter(*this, size()); }; +}; + +String operator+(const String& lhs, const String& rhs); +String operator+(const String& lhs, const char* rhs); +String operator+(const String& lhs, uint8_t rhs); +String operator+(const String& lhs, std::string_view view); + +bool operator==(const String& lhs, const String& rhs); +bool operator==(const String& lhs, const char* rhs); +// bool operator==(const String& lhs, std::string_view rhs); +bool operator!=(const String& lhs, const String& rhs); +bool operator!=(const String& lhs, const char* rhs); +// bool operator!=(const String& lhs, std::string_view rhs); +bool operator<(const String& lhs, const String& rhs); +bool operator<(const String& lhs, const char* rhs); +// bool operator<(const String& lhs, std::string_view rhs); +bool operator<=(const String& lhs, const String& rhs); +bool operator<=(const String& lhs, const char* rhs); +// bool operator<=(const String& lhs, std::string_view rhs); +bool operator>(const String& lhs, const String& rhs); +bool operator>(const String& lhs, const char* rhs); +// bool operator>(const String& lhs, std::string_view rhs); +bool operator>=(const String& lhs, const String& rhs); +bool operator>=(const String& lhs, const char* rhs); +// bool operator>=(const String& lhs, std::string_view rhs); + +Vector to_utf16(std::string_view utf8); +Vector to_utf32(std::string_view utf8); + +srb2::StaticVec to_utf8(uint32_t codepoint); + +template < + typename ItUTF32, + typename std::enable_if_t< + std::is_constructible< + uint32_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 +> +srb2::String to_utf8(ItUTF32 begin, ItUTF32 end) +{ + srb2::String ret; + for (auto itr = begin; itr != end; ++itr) + { + srb2::StaticVec utf8 = to_utf8(*itr); + ret.append(utf8.begin(), utf8.end()); + } + return ret; +} + +srb2::String to_utf8(std::u32string_view utf32view); + +// fmtlib +inline auto format_as(const String& str) { return fmt::string_view(static_cast(str)); } + +srb2::String vformat(fmt::string_view fmt, fmt::format_args args); + +template +inline auto format(fmt::format_string fmt, T&&... args) +{ + return ::srb2::vformat(fmt, fmt::vargs{{args...}}); +} + +} // namespace srb2 + +namespace std +{ + +template <> struct hash +{ + size_t operator()(const srb2::String& v); +}; + +} // namespace std + +#endif // __cplusplus + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +int Str_IsValidUTF8(const char* str); +// int Str_ToUTF16(uint16_t* dst, size_t dstlen, const char* src); +// int Str_ToUTF32(uint32_t* dst, size_t dstlen, const char* src); +uint32_t Str_NextCodepointFromUTF8(const char** itr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SRB2_CORE_STRING_HPP diff --git a/src/core/thread_pool.cpp b/src/core/thread_pool.cpp index eabd46c70..8a434ba63 100644 --- a/src/core/thread_pool.cpp +++ b/src/core/thread_pool.cpp @@ -14,12 +14,13 @@ #include #include #include -#include #include #include #include +#include "../core/string.h" +#include "../core/vector.hpp" #include "../cxxutil.hpp" #include "../m_argv.h" @@ -50,11 +51,11 @@ static void pool_executor( std::shared_ptr worker_ready_mutex, std::shared_ptr worker_ready_condvar, std::shared_ptr my_wq, - std::vector> other_wqs + srb2::Vector> other_wqs ) { { - std::string thread_name = fmt::format("Thread Pool Thread {}", thread_index); + srb2::String thread_name = srb2::format("Thread Pool Thread {}", thread_index); tracy::SetThreadName(thread_name.c_str()); } @@ -133,7 +134,7 @@ ThreadPool::ThreadPool(size_t threads) for (size_t i = 0; i < threads; i++) { std::shared_ptr my_queue = work_queues_[i]; - std::vector> other_queues; + srb2::Vector> other_queues; for (size_t j = 0; j < threads; j++) { // Order the other queues starting from the next adjacent worker diff --git a/src/core/vector.cpp b/src/core/vector.cpp new file mode 100644 index 000000000..2321ebcc2 --- /dev/null +++ b/src/core/vector.cpp @@ -0,0 +1,61 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "vector.hpp" + +#include +#include +#include + +#include "string.h" + +namespace srb2 +{ + +AbstractVector::GrowResult AbstractVector::_realloc_mem(void* data, size_t size, size_t old_cap, size_t elem_size, Move move, size_t cap) noexcept +{ + GrowResult ret; + std::allocator allocator; + + if (old_cap == 0) + { + cap = std::max(cap, 1); + ret.data = allocator.allocate(cap * elem_size + sizeof(std::max_align_t)); + ret.cap = cap; + return ret; + } + + cap = std::max(cap, old_cap * 2); + ret.data = allocator.allocate(cap * elem_size + sizeof(std::max_align_t)); + (move)(ret.data, data, size); + allocator.deallocate(reinterpret_cast(data), old_cap * elem_size + sizeof(std::max_align_t)); + ret.cap = cap; + return ret; +} + +void AbstractVector::_free_mem(void* data, size_t cap, size_t elem_size) noexcept +{ + std::allocator allocator; + allocator.deallocate(reinterpret_cast(data), cap * elem_size + sizeof(std::max_align_t)); +} + +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; + +} // namespace srb2 diff --git a/src/core/vector.hpp b/src/core/vector.hpp new file mode 100644 index 000000000..e6d66f91f --- /dev/null +++ b/src/core/vector.hpp @@ -0,0 +1,366 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_VEC_HPP +#define SRB2_CORE_VEC_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace srb2 +{ + +class AbstractVector +{ +protected: + size_t size_; + size_t capacity_; + size_t elem_size_; + void* data_; + + using Move = void(*)(void* dst, void* src, size_t size); + + template + static void _move(void* dst, void* src, size_t size) noexcept + { + T* d = (T*)dst; + T* s = (T*)src; + for (size_t i = 0; i < size; i++) + { + new (&d[i]) T(std::move(s[i])); + } + } + + struct GrowResult + { + void* data; + size_t cap; + }; + static GrowResult _realloc_mem(void* data, size_t size, size_t old_cap, size_t elem_size, Move move, size_t cap) noexcept; + static void _free_mem(void* data, size_t cap, size_t elem_size) noexcept; + + template + void _reserve_mem(size_t c) noexcept + { + if (c > capacity_) + { + GrowResult r = _realloc_mem(data_, size_, capacity_, elem_size_, _move, c); + data_ = r.data; + capacity_ = r.cap; + } + } + + constexpr AbstractVector() : size_(0), capacity_(0), elem_size_(0), data_(nullptr) {} +}; + +template +class Vector : AbstractVector +{ +public: + // iter traits + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + Vector() : AbstractVector() + { + elem_size_ = sizeof(T); + } + Vector(const Vector& rhs) : Vector() + { + *this = rhs; + } + Vector(Vector&& rhs) noexcept : AbstractVector() + { + elem_size_ = sizeof(T); + *this = std::move(rhs); + } + ~Vector() + { + if (data_) + { + for (size_type i = 0; i < size_; i++) + { + ((T*)(data_))[(size_ - i - 1)].~T(); + } + _free_mem(data_, capacity_, elem_size_); + } + data_ = nullptr; + } + + explicit Vector(size_type capacity) : AbstractVector() + { + elem_size_ = sizeof(T); + _reserve_mem(capacity); + } + Vector(std::initializer_list l) : AbstractVector() + { + elem_size_ = sizeof(T); + if (l.size() == 0) + { + return; + } + + _reserve_mem(l.size()); + for (auto itr = l.begin(); itr != l.end(); itr++) + { + emplace_back(*itr); + } + } + + template < + typename It, + typename std::enable_if_t< + std::is_constructible_v< + T, + typename std::iterator_traits::reference + >, + int + > = 0 + > + Vector(It begin, It end) : AbstractVector() + { + elem_size_ = sizeof(T); + for (auto itr = begin; itr != end; ++itr) + { + push_back(*itr); + } + } + + Vector& operator=(const Vector& rhs) + { + for (auto itr = rhs.begin(); itr != rhs.end(); itr++) + { + push_back(*itr); + } + return *this; + } + + Vector& operator=(Vector&& rhs) noexcept + { + std::swap(size_, rhs.size_); + std::swap(capacity_, rhs.capacity_); + std::swap(data_, rhs.data_); + return *this; + } + + constexpr size_type size() const noexcept { return size_; } + constexpr size_type capacity() const noexcept { return capacity_; } + constexpr bool empty() const noexcept { return size_ == 0; } + + constexpr T* data() noexcept { return (T*) data_; } + constexpr const T* data() const noexcept { return (const T*) data_; } + constexpr T* begin() noexcept { return data(); } + constexpr const T* begin() const noexcept { return data(); } + constexpr T* end() noexcept { return data() + size(); } + constexpr const T* end() const noexcept { return data() + size(); } + constexpr const T* cbegin() const noexcept { return data(); } + constexpr const T* cend() const noexcept { return end(); } + constexpr auto rbegin() noexcept { return std::reverse_iterator(end()); } + constexpr auto rbegin() const noexcept { return std::reverse_iterator(end()); } + constexpr auto rend() noexcept { return std::reverse_iterator(begin()); } + constexpr auto rend() const noexcept { return std::reverse_iterator(begin()); } + constexpr auto crbegin() const noexcept { return rbegin(); } + constexpr auto crend() const noexcept { return rend(); } + T& front() noexcept { return data()[0]; } + const T& front() const noexcept { return data()[0]; } + T& back() noexcept { return data()[size() - 1]; } + const T& back() const noexcept { return data()[size() - 1]; } + + void push_back(const T& t) + { + reserve(size() + 1); + new (&data()[size()]) T(t); + size_++; + } + + void push_back(T&& t) + { + reserve(size() + 1); + new (&data()[size()]) T(std::move(t)); + size_++; + } + + void pop_back() + { + T* end = &data()[size() - 1]; + end->~T(); + size_--; + } + + void clear() { for (auto& x : *this) x.~T(); size_ = 0; } + + void reserve(size_type c) + { + _reserve_mem(c); + } + + void resize(size_type s) + { + resize(s, T()); + } + + void resize(size_type s, const T& value) + { + if (s <= size()) + { + auto itr_begin = rbegin(); + auto itr_end = std::prev(rend(), s); + size_t count_destroyed = 0; + for (auto itr = itr_begin; itr != itr_end; itr++) + { + itr->~T(); + count_destroyed++; + } + size_ = s; + } + else + { + reserve(s); + size_type oldsize = size(); + size_ = s; + for (auto itr = std::next(begin(), oldsize); itr != end(); itr++) + { + try + { + new (itr) T(value); + } + catch (...) + { + size_ = oldsize; + std::rethrow_exception(std::current_exception()); + } + } + } + } + + const T& at(size_type i) const { if (i >= size()) throw std::out_of_range("index out of range"); return data()[i]; } + T& at(size_type i) { if (i >= size()) throw std::out_of_range("index out of range"); return data()[i]; } + + T& operator[](size_type i) { return data()[i]; } + const T& operator[](size_type i) const { return data()[i]; } + + iterator erase(const_iterator first) { return erase(first, first + 1); } + iterator erase(const_iterator first, const_iterator last) + { + iterator firstm = const_cast(first); + iterator lastm = const_cast(last); + if (first == last) return firstm; + + auto diff = last - first; + if (last != end()) std::move(lastm, end(), firstm); + resize(size_ - diff); + + return firstm; + } + + iterator insert(const_iterator pos, const T& value) + { + return insert(pos, (size_type)1, std::forward(value)); + } + + iterator insert(const_iterator pos, T&& value) + { + size_type oldsize = size(); + difference_type offs = pos - data(); + push_back(std::move(value)); + std::rotate(data() + offs, data() + oldsize, data() + size()); + return data() + offs; + } + + iterator insert(const_iterator pos, size_type count, const T& value) + { + size_type oldsize = size(); + difference_type offs = pos - data(); + reserve(oldsize + count); + T* d = data(); + for (uint32_t i = 0; i < count; i++) + { + // must be copy-initialized; + // value is currently uninitialized + new ((T*)(&d[oldsize + i])) T(value); + } + size_ = oldsize + count; + std::rotate(d + offs, d + oldsize, d + (oldsize + count)); + return data() + offs; + } + + template < + class InputIt, + typename std::enable_if_t< + std::is_constructible< + T, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + iterator insert(const_iterator pos, InputIt first, InputIt last) + { + size_type oldsize = size(); + difference_type offs = pos - data(); + + size_type count = 0; + while (first != last) + { + push_back(*first); + ++first; + ++count; + } + std::rotate(data() + offs, data() + oldsize, data() + (oldsize + count)); + return data() + offs; + } + + template + iterator emplace(const_iterator position, A&&... args) + { + return insert(position, T(std::forward(args)...)); + } + + template + T& emplace_back(A&&... args) + { + reserve(size() + 1); + new (&data()[size()]) T(std::forward(args)...); + size_++; + return (*this)[size_ - 1]; + } +}; + +class String; + +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; + +} // namespace srb2 + +#endif // SRB2_CORE_VEC_HPP diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 06a6a4089..1d36dcb7e 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -16,7 +16,6 @@ #include #include -#include #include "doomdef.h" #include "doomtype.h" @@ -50,6 +49,7 @@ #include "md5.h" // demo checksums #include "p_saveg.h" // savebuffer_t #include "g_party.h" +#include "core/json.hpp" // SRB2Kart #include "d_netfil.h" // nameonly @@ -2437,17 +2437,18 @@ void G_BeginRecording(void) void srb2::write_current_demo_standings(const srb2::StandingsJson& standings) { using namespace srb2; - using json = nlohmann::json; // TODO populate standings data - std::vector ubjson = json::to_ubjson(standings); + JsonValue value { JsonObject() }; + to_json(value, standings); + Vector ubjson = value.to_ubjson(); uint32_t bytes = ubjson.size(); WRITEUINT8(demobuf.p, DW_STANDING2); WRITEUINT32(demobuf.p, bytes); - WRITEMEM(demobuf.p, ubjson.data(), bytes); + WRITEMEM(demobuf.p, (UINT8*)ubjson.data(), bytes); } void srb2::write_current_demo_end_marker() @@ -2615,12 +2616,12 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) static bool load_ubjson_standing(menudemo_t* pdemo, tcb::span slice, tcb::span demoskins) { using namespace srb2; - using json = nlohmann::json; StandingsJson js; try { - js = json::from_ubjson(slice).template get(); + JsonValue value { JsonValue::from_ubjson(tcb::as_bytes(slice)) }; + from_json(value, js); } catch (...) { diff --git a/src/g_demo.h b/src/g_demo.h index 89bc8d227..e3c4c74ff 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -21,10 +21,9 @@ #ifdef __cplusplus -#include -#include - -#include +#include "core/json.hpp" +#include "core/string.h" +#include "core/vector.hpp" // Modern json formats namespace srb2 @@ -32,12 +31,12 @@ namespace srb2 struct StandingJson { uint8_t ranking; - std::string name; + String name; uint8_t demoskin; - std::string skincolor; + String skincolor; uint32_t timeorscore; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( StandingJson, ranking, name, @@ -48,9 +47,9 @@ struct StandingJson }; struct StandingsJson { - std::vector standings; + Vector standings; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(StandingsJson, standings) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(StandingsJson, standings) }; void write_current_demo_standings(const StandingsJson& standings); diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index 20ba10530..58a84685c 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -27,7 +27,6 @@ #include "z_zone.h" namespace fs = std::filesystem; -using json = nlohmann::json; #define GD_VERSION_MAJOR (0xBA5ED321) #define GD_VERSION_MINOR (1) @@ -137,13 +136,13 @@ void srb2::save_ng_gamedata() skin_t& memskin = skins[i]; auto skin = skintojson(&memskin.records); - std::string name = std::string(memskin.name); + srb2::String name { memskin.name }; ng.skins[name] = std::move(skin); } for (auto unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next) { auto skin = skintojson(&unloadedskin->records); - std::string name = std::string(unloadedskin->name); + srb2::String name { unloadedskin->name }; ng.skins[name] = std::move(skin); } @@ -175,13 +174,13 @@ void srb2::save_ng_gamedata() for (int i = 0; i < nummapheaders; i++) { auto map = maptojson(&mapheaderinfo[i]->records); - std::string lumpname = std::string(mapheaderinfo[i]->lumpname); + srb2::String lumpname { mapheaderinfo[i]->lumpname }; ng.maps[lumpname] = std::move(map); } for (auto unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next) { auto map = maptojson(&unloadedmap->records); - std::string lumpname = std::string(unloadedmap->lumpname); + srb2::String lumpname { unloadedmap->lumpname }; ng.maps[lumpname] = std::move(map); } for (int i = 0; i < gamedata->numspraycans; i++) @@ -194,7 +193,7 @@ void srb2::save_ng_gamedata() { continue; } - spraycan.color = std::string(skincolors[can->col].name); + spraycan.color = String(skincolors[can->col].name); if (can->map == NEXTMAP_INVALID) { @@ -213,7 +212,7 @@ void srb2::save_ng_gamedata() { continue; } - spraycan.map = std::string(mapheader->lumpname); + spraycan.map = String(mapheader->lumpname); ng.spraycans.emplace_back(std::move(spraycan)); } @@ -230,11 +229,11 @@ void srb2::save_ng_gamedata() skinreference_t& skinref = windata[i].best_skin; if (skinref.unloaded) { - newrecords.bestskin = std::string(skinref.unloaded->name); + newrecords.bestskin = String(skinref.unloaded->name); } else { - newrecords.bestskin = std::string(skins[skinref.id].name); + newrecords.bestskin = String(skins[skinref.id].name); } newrecords.gotemerald = windata[i].got_emerald; @@ -252,7 +251,7 @@ void srb2::save_ng_gamedata() } auto cupdata = cuptojson(cup->windata); - cupdata.name = std::string(cup->name); + cupdata.name = String(cup->name); ng.cups[cupdata.name] = std::move(cupdata); } for (auto unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next) @@ -263,7 +262,7 @@ void srb2::save_ng_gamedata() } auto cupdata = cuptojson(unloadedcup->windata); - cupdata.name = std::string(unloadedcup->name); + cupdata.name = String(unloadedcup->name); ng.cups[cupdata.name] = std::move(cupdata); } @@ -273,17 +272,19 @@ void srb2::save_ng_gamedata() cupheader_t* cup = gamedata->sealedswaps[i]; - sealedswap.name = std::string(cup->name); + sealedswap.name = String(cup->name); ng.sealedswaps.emplace_back(std::move(sealedswap)); } - std::string gamedataname_s {gamedatafilename}; - fs::path savepath {fmt::format("{}/{}", srb2home, gamedataname_s)}; - fs::path baksavepath {fmt::format("{}/{}.bak", srb2home, gamedataname_s)}; - - json ngdata_json = ng; + String gamedataname_s {gamedatafilename}; + String savepath_string = srb2::format("{}/{}", srb2home, gamedataname_s); + String baksavepath_string = srb2::format("{}/{}.bak", srb2home, gamedataname_s); + fs::path savepath { static_cast(savepath_string) }; + fs::path baksavepath { static_cast(srb2::format("{}/{}.bak", srb2home, gamedataname_s)) }; + JsonValue ngdata_json { JsonObject() }; + to_json(ngdata_json, ng); if (fs::exists(savepath)) { @@ -300,7 +301,7 @@ void srb2::save_ng_gamedata() try { - std::string savepathstring = savepath.string(); + String savepathstring = savepath.string(); srb2::io::FileStream file {savepathstring, srb2::io::FileStreamMode::kWrite}; // The header is necessary to validate during loading. @@ -308,7 +309,7 @@ void srb2::save_ng_gamedata() srb2::io::write(static_cast(GD_VERSION_MINOR), file); // minor/flags srb2::io::write(static_cast(gamedata->evercrashed), file); // dirty (crash recovery) - std::vector ubjson = json::to_ubjson(ng); + srb2::Vector ubjson = ngdata_json.to_ubjson(); srb2::io::write_exact(file, tcb::as_bytes(tcb::make_span(ubjson))); file.close(); } @@ -382,7 +383,7 @@ void srb2::load_ng_gamedata() return; } - std::string datapath {fmt::format("{}/{}", srb2home, gamedatafilename)}; + String datapath {srb2::format("{}/{}", srb2home, gamedatafilename)}; srb2::io::BufferedInputStream bis; try @@ -419,15 +420,13 @@ void srb2::load_ng_gamedata() return; } - std::vector remainder = srb2::io::read_to_vec(bis); + srb2::Vector remainder = srb2::io::read_to_vec(bis); GamedataJson js; try { - // safety: std::byte repr is always uint8_t 1-byte aligned - tcb::span remainder_as_u8 = tcb::span((uint8_t*)remainder.data(), remainder.size()); - json parsed = json::from_ubjson(remainder_as_u8); - js = parsed.template get(); + JsonValue parsed = JsonValue::from_ubjson(remainder); + from_json(parsed, js); } catch (const std::exception& ex) { @@ -542,7 +541,7 @@ void srb2::load_ng_gamedata() gamedata->challengegrid = static_cast(Z_Malloc( (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT16)), PU_STATIC, NULL)); - for (size_t i = 0; i < std::min((size_t)(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT), js.challengegrid.grid.size()); i++) + for (size_t i = 0; i < std::min((size_t)(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT), js.challengegrid.grid.size()); i++) { uint16_t gridvalue = js.challengegrid.grid[i]; gamedata->challengegrid[i] = gridvalue; @@ -745,7 +744,7 @@ void srb2::load_ng_gamedata() // Find the loaded cup for (cup = kartcupheaders; cup; cup = cup->next) { - std::string cupname = std::string(cup->name); + String cupname { cup->name }; if (cupname == cuppair.first) { break; @@ -770,7 +769,7 @@ void srb2::load_ng_gamedata() } for (auto unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next) { - std::string skinname = std::string(unloadedskin->name); + String skinname { unloadedskin->name }; if (skinname == cuppair.second.records[j].bestskin) { skinreference_t ref {}; @@ -826,7 +825,7 @@ void srb2::load_ng_gamedata() break; } - std::string cupname = std::string(cup->name); + String cupname { cup->name }; if (cupname == js.sealedswaps[i].name) { break; diff --git a/src/g_gamedata.h b/src/g_gamedata.h index 130252692..bde624c4b 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -13,13 +13,12 @@ #ifdef __cplusplus -#include #include -#include -#include -#include -#include +#include "core/json.hpp" +#include "core/hash_map.hpp" +#include "core/string.h" +#include "core/vector.hpp" namespace srb2 { @@ -39,7 +38,7 @@ struct GamedataPlaytimeJson final uint32_t statistics; uint32_t tumble; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataPlaytimeJson, total, netgame, @@ -60,7 +59,7 @@ struct GamedataRingsJson final { uint32_t total; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRingsJson, total) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRingsJson, total) }; struct GamedataRoundsJson final @@ -71,7 +70,7 @@ struct GamedataRoundsJson final uint32_t special; uint32_t custom; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRoundsJson, race, battle, prisons, special, custom) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRoundsJson, race, battle, prisons, special, custom) }; struct GamedataChallengeKeysJson final @@ -81,7 +80,7 @@ struct GamedataChallengeKeysJson final uint16_t keyspending; uint16_t chaokeys; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeKeysJson, pendingkeyrounds, pendingkeyroundoffset, keyspending, chaokeys) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeKeysJson, pendingkeyrounds, pendingkeyroundoffset, keyspending, chaokeys) }; struct GamedataMilestonesJson final @@ -98,7 +97,7 @@ struct GamedataMilestonesJson final bool sealedswapalerted; bool tutorialdone; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMilestonesJson, gonerlevel, everloadedaddon, @@ -119,15 +118,15 @@ struct GamedataPrisonEggPickupsJson final uint16_t thisprisoneggpickup; uint16_t prisoneggstothispickup; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataPrisonEggPickupsJson, thisprisoneggpickup, prisoneggstothispickup) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataPrisonEggPickupsJson, thisprisoneggpickup, prisoneggstothispickup) }; struct GamedataChallengeGridJson final { uint32_t width; - std::vector grid; + Vector grid; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeGridJson, width, grid) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeGridJson, width, grid) }; struct GamedataSkinRecordsPlaytimeJson final @@ -140,7 +139,7 @@ struct GamedataSkinRecordsPlaytimeJson final uint32_t custom; uint32_t tumble; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataSkinRecordsPlaytimeJson, total, race, @@ -158,7 +157,7 @@ struct GamedataSkinRecordsJson final uint32_t rounds; GamedataSkinRecordsPlaytimeJson time; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataSkinRecordsJson, wins, rounds, @@ -170,7 +169,7 @@ struct GamedataSkinJson final { GamedataSkinRecordsJson records; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSkinJson, records) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSkinJson, records) }; struct GamedataMapVisitedJson final @@ -181,7 +180,7 @@ struct GamedataMapVisitedJson final bool spbattack; bool mysticmelody; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapVisitedJson, visited, beaten, encore, spbattack, mysticmelody) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapVisitedJson, visited, beaten, encore, spbattack, mysticmelody) }; struct GamedataMapStatsTimeAttackJson final @@ -189,7 +188,7 @@ struct GamedataMapStatsTimeAttackJson final uint32_t besttime; uint32_t bestlap; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsTimeAttackJson, besttime, bestlap) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsTimeAttackJson, besttime, bestlap) }; struct GamedataMapStatsSpbAttackJson final @@ -197,7 +196,7 @@ struct GamedataMapStatsSpbAttackJson final uint32_t besttime; uint32_t bestlap; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsSpbAttackJson, besttime, bestlap) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsSpbAttackJson, besttime, bestlap) }; struct GamedataMapStatsPlaytimeJson final @@ -212,7 +211,7 @@ struct GamedataMapStatsPlaytimeJson final uint32_t timeattack; uint32_t spbattack; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMapStatsPlaytimeJson, total, netgame, @@ -232,7 +231,7 @@ struct GamedataMapStatsJson final GamedataMapStatsSpbAttackJson spbattack; GamedataMapStatsPlaytimeJson time; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMapStatsJson, timeattack, spbattack, @@ -245,15 +244,15 @@ struct GamedataMapJson final GamedataMapVisitedJson visited; GamedataMapStatsJson stats; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapJson, visited, stats) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapJson, visited, stats) }; struct GamedataSprayCanJson final { - std::string map; - std::string color; + String map; + String color; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSprayCanJson, map, color) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSprayCanJson, map, color) }; struct GamedataCupRecordsJson final @@ -261,24 +260,24 @@ struct GamedataCupRecordsJson final uint8_t bestplacement; uint8_t bestgrade; bool gotemerald; - std::string bestskin; + String bestskin; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupRecordsJson, bestplacement, bestgrade, gotemerald, bestskin) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupRecordsJson, bestplacement, bestgrade, gotemerald, bestskin) }; struct GamedataCupJson final { - std::string name; - std::vector records; + String name; + Vector records; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records) }; struct GamedataSealedSwapJson final { - std::string name; + String name; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name) }; struct GamedataJson final @@ -290,19 +289,19 @@ struct GamedataJson final GamedataMilestonesJson milestones; GamedataPrisonEggPickupsJson prisons; uint32_t tafolderhash; - std::vector emblems; - std::vector unlockables; - std::vector unlockpending; - std::vector conditionsets; + Vector emblems; + Vector unlockables; + Vector unlockpending; + Vector conditionsets; GamedataChallengeGridJson challengegrid; uint32_t timesBeaten; - std::unordered_map skins; - std::unordered_map maps; - std::vector spraycans; - std::unordered_map cups; - std::vector sealedswaps; + HashMap skins; + HashMap maps; + Vector spraycans; + HashMap cups; + Vector sealedswaps; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataJson, playtime, rings, diff --git a/src/hud/input-display.cpp b/src/hud/input-display.cpp index 383d19a62..fbfd03ee2 100644 --- a/src/hud/input-display.cpp +++ b/src/hud/input-display.cpp @@ -85,8 +85,8 @@ void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boo const ticcmd_t& cmd = players[displayplayers[pid]].cmd; const boolean analog = (mode == '4' || mode == '5') ? players[displayplayers[pid]].analoginput : false; - const std::string prefix = fmt::format("PR{}", mode); - auto gfx = [&](auto format, auto&&... args) { return prefix + fmt::format(format, args...); }; + srb2::String prefix = srb2::format("PR{}", mode); + auto gfx = [&](auto format, auto&&... args) { return prefix + srb2::format(format, args...); }; auto but = [&](char key, INT32 gc, UINT32 bt) { bool press = local ? G_PlayerInputAnalog(pid, gc, guessinput) : ((cmd.buttons & bt) == bt); diff --git a/src/hud/spectator.cpp b/src/hud/spectator.cpp index c20d05cf3..f0606378a 100644 --- a/src/hud/spectator.cpp +++ b/src/hud/spectator.cpp @@ -165,7 +165,7 @@ void K_drawSpectatorHUD(boolean director) if (player) { - std::string label = [player] + srb2::String label = [player] { if (player->flashing) { @@ -183,7 +183,7 @@ void K_drawSpectatorHUD(boolean director) if (cv_maxplayers.value) { - label += fmt::format(" [{}/{}]", numingame, cv_maxplayers.value); + label += srb2::format(" [{}/{}]", numingame, cv_maxplayers.value); } list.insert({{label.c_str(), ""}}); diff --git a/src/hwr2/pass_resource_managers.hpp b/src/hwr2/pass_resource_managers.hpp index 7d03d950e..9636480dd 100644 --- a/src/hwr2/pass_resource_managers.hpp +++ b/src/hwr2/pass_resource_managers.hpp @@ -13,9 +13,9 @@ #include #include -#include -#include +#include "../core/hash_map.hpp" +#include "../core/vector.hpp" #include "../rhi/rhi.hpp" namespace srb2::hwr2 @@ -77,10 +77,10 @@ class MainPaletteManager final rhi::Handle encore_lighttable_; rhi::Handle default_colormap_; - std::unordered_map> colormaps_; - std::unordered_map> lighttables_; - std::vector colormaps_to_upload_; - std::vector lighttables_to_upload_; + srb2::HashMap> colormaps_; + srb2::HashMap> lighttables_; + srb2::Vector colormaps_to_upload_; + srb2::Vector lighttables_to_upload_; void upload_palette(rhi::Rhi& rhi); void upload_lighttables(rhi::Rhi& rhi); diff --git a/src/hwr2/patch_atlas.cpp b/src/hwr2/patch_atlas.cpp index fb6b4d965..5dad04e5f 100644 --- a/src/hwr2/patch_atlas.cpp +++ b/src/hwr2/patch_atlas.cpp @@ -72,7 +72,7 @@ rhi::Rect srb2::hwr2::trimmed_patch_dimensions(const patch_t* patch) return {minx, miny, static_cast(maxx - minx), static_cast(maxy - miny)}; } -void srb2::hwr2::convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector& out) +void srb2::hwr2::convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, srb2::Vector& out) { Rect trimmed_rect = srb2::hwr2::trimmed_patch_dimensions(patch); if (trimmed_rect.w % 2 > 0) @@ -299,7 +299,7 @@ void PatchAtlasCache::pack(Rhi& rhi) SRB2_ASSERT(ready_for_lookup()); // Upload atlased patches - std::vector patch_data; + srb2::Vector patch_data; for (const patch_t* patch_to_upload : patches_to_upload_) { srb2::NotNull atlas = find_patch(patch_to_upload); diff --git a/src/hwr2/patch_atlas.hpp b/src/hwr2/patch_atlas.hpp index e19ff4345..46e39740d 100644 --- a/src/hwr2/patch_atlas.hpp +++ b/src/hwr2/patch_atlas.hpp @@ -14,12 +14,12 @@ #include #include #include -#include -#include -#include #include +#include "../core/hash_map.hpp" +#include "../core/hash_set.hpp" +#include "../core/vector.hpp" #include "../r_defs.h" #include "../rhi/rhi.hpp" @@ -55,7 +55,7 @@ private: rhi::Handle tex_; uint32_t size_; - std::unordered_map entries_; + srb2::HashMap entries_; std::unique_ptr rp_ctx {nullptr}; std::unique_ptr rp_nodes {nullptr}; @@ -84,11 +84,11 @@ public: /// drawing things like sprites and 2D elements. class PatchAtlasCache { - std::vector atlases_; - std::unordered_map patch_lookup_; + srb2::Vector atlases_; + srb2::HashMap patch_lookup_; - std::unordered_set patches_to_pack_; - std::unordered_set patches_to_upload_; + srb2::HashSet patches_to_pack_; + srb2::HashSet patches_to_upload_; uint32_t tex_size_ = 2048; size_t max_textures_ = 2; @@ -135,7 +135,7 @@ rhi::Rect trimmed_patch_dimensions(const patch_t* patch); /// during upload, but required for the RHI device's Unpack Alignment of 4 bytes. /// @param patch the patch to convert /// @param out the output vector, cleared before writing. -void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector& out); +void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, srb2::Vector& out); } // namespace srb2::hwr2 diff --git a/src/hwr2/postprocess_wipe.cpp b/src/hwr2/postprocess_wipe.cpp index d0adf2349..86364ab23 100644 --- a/src/hwr2/postprocess_wipe.cpp +++ b/src/hwr2/postprocess_wipe.cpp @@ -10,12 +10,11 @@ #include "postprocess_wipe.hpp" -#include - #include #include #include +#include "../core/string.h" #include "../f_finale.h" #include "../w_wad.h" @@ -106,7 +105,7 @@ void PostprocessWipePass::prepass(Rhi& rhi) return; } - std::string lumpname = fmt::format(FMT_STRING("FADE{:02d}{:02d}"), wipe_type, wipe_frame); + String lumpname = format(FMT_STRING("FADE{:02d}{:02d}"), wipe_type, wipe_frame); lumpnum_t mask_lump = W_CheckNumForName(lumpname.c_str()); if (mask_lump == LUMPERROR) { diff --git a/src/hwr2/resource_management.cpp b/src/hwr2/resource_management.cpp index 850bebfb2..0ac7d7358 100644 --- a/src/hwr2/resource_management.cpp +++ b/src/hwr2/resource_management.cpp @@ -181,7 +181,7 @@ Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, lumpnum_t l }); flats_.insert({lump, new_tex}); - std::vector> flat_data; + srb2::Vector> flat_data; std::size_t lump_length = W_LumpLength(lump); flat_data.reserve(flat_size * flat_size); diff --git a/src/hwr2/resource_management.hpp b/src/hwr2/resource_management.hpp index cf69d7591..3fd6666cf 100644 --- a/src/hwr2/resource_management.hpp +++ b/src/hwr2/resource_management.hpp @@ -11,10 +11,10 @@ #ifndef __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__ #define __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__ +#include "../core/hash_map.hpp" +#include "../core/vector.hpp" #include "../rhi/rhi.hpp" -#include - namespace srb2::hwr2 { @@ -27,8 +27,8 @@ class PaletteManager #endif rhi::Handle default_colormap_; - std::unordered_map> colormaps_; - std::unordered_map> lighttables_; + srb2::HashMap> colormaps_; + srb2::HashMap> lighttables_; public: PaletteManager(); @@ -67,9 +67,9 @@ from patch_t. /// @brief Manages textures corresponding to specific flats indexed by lump number. class FlatTextureManager { - std::unordered_map> flats_; - std::vector to_upload_; - std::vector> disposed_textures_; + srb2::HashMap> flats_; + srb2::Vector to_upload_; + srb2::Vector> disposed_textures_; public: FlatTextureManager(); diff --git a/src/hwr2/twodee_renderer.cpp b/src/hwr2/twodee_renderer.cpp index 5cf18e3b9..020b8306d 100644 --- a/src/hwr2/twodee_renderer.cpp +++ b/src/hwr2/twodee_renderer.cpp @@ -11,11 +11,11 @@ #include "twodee_renderer.hpp" #include -#include #include #include +#include "../core/hash_set.hpp" #include "blendmode.hpp" #include "../r_patch.h" #include "../v_video.h" @@ -248,7 +248,7 @@ void TwodeeRenderer::flush(Rhi& rhi, Twodee& twodee) } // Stage 1 - command list patch detection - std::unordered_set found_patches; + srb2::HashSet found_patches; for (const auto& list : twodee) { for (const auto& cmd : list.cmds) diff --git a/src/io/streams.hpp b/src/io/streams.hpp index ab0da285a..e46093e93 100644 --- a/src/io/streams.hpp +++ b/src/io/streams.hpp @@ -12,16 +12,16 @@ #define __SRB2_IO_STREAMS_HPP__ #include -#include #include -#include #include #include -#include #include #include +#include "../core/string.h" +#include "../core/vector.hpp" + namespace srb2::io { @@ -484,13 +484,13 @@ inline void read_exact(SpanStream& stream, tcb::span buffer) } class VecStream { - std::vector vec_; + srb2::Vector vec_; std::size_t head_ {0}; public: VecStream() = default; - VecStream(const std::vector& vec) : vec_(vec) {} - VecStream(std::vector&& vec) : vec_(std::move(vec)) {} + VecStream(const srb2::Vector& vec) : vec_(vec) {} + VecStream(srb2::Vector&& vec) : vec_(std::move(vec)) {} VecStream(const VecStream& rhs) = default; VecStream(VecStream&& rhs) = default; @@ -549,7 +549,7 @@ public: return head_; } - std::vector& vector() { return vec_; } + srb2::Vector& vector() { return vec_; } friend void read_exact(VecStream& stream, tcb::span buffer); }; @@ -674,7 +674,7 @@ template buf_; + srb2::Vector buf_; std::size_t buf_head_; bool zstream_initialized_; bool zstream_ended_; @@ -820,7 +820,7 @@ template buf_; + srb2::Vector buf_; tcb::span::size_type cap_; public: @@ -872,7 +872,7 @@ template buf_; + srb2::Vector buf_; tcb::span::size_type cap_; public: @@ -933,7 +933,7 @@ extern template class BufferedInputStream; template StreamSize pipe_all(I& input, O& output) { - std::vector buf; + srb2::Vector buf; StreamSize total_written = 0; StreamSize read_this_time = 0; @@ -951,7 +951,7 @@ StreamSize pipe_all(I& input, O& output) { } template -std::vector read_to_vec(I& input) { +srb2::Vector read_to_vec(I& input) { VecStream out; pipe_all(input, out); return std::move(out.vector()); diff --git a/src/k_bans.cpp b/src/k_bans.cpp index bbc86ee57..673adf0b7 100644 --- a/src/k_bans.cpp +++ b/src/k_bans.cpp @@ -12,13 +12,14 @@ /// \file k_bans.c /// \brief replacement for DooM Legacy ban system -#include #include -#include #include -#include +#include +#include "core/json.hpp" +#include "core/string.h" +#include "io/streams.hpp" #include "i_tcp_detail.h" // clientaddress #include "k_bans.h" #include "byteptr.h" // READ/WRITE macros @@ -34,7 +35,9 @@ #include "z_zone.h" #include "i_addrinfo.h" // I_getaddrinfo -using nlohmann::json; +using srb2::JsonArray; +using srb2::JsonObject; +using srb2::JsonValue; static mysockaddr_t *DuplicateSockAddr(const mysockaddr_t *source) { @@ -43,22 +46,22 @@ static mysockaddr_t *DuplicateSockAddr(const mysockaddr_t *source) return dup; } -static std::vector bans; +static srb2::Vector bans; static uint8_t allZero[PUBKEYLENGTH]; -static void load_bans_array_v1(json& array) +static void load_bans_array_v1(const JsonArray& array) { - for (json& object : array) + for (const JsonValue& object : array) { uint8_t public_key_bin[PUBKEYLENGTH]; - std::string public_key = object.at("public_key"); - std::string ip_address = object.at("ip_address"); - time_t expires = object.at("expires"); - UINT8 subnet_mask = object.at("subnet_mask"); - std::string username = object.at("username"); - std::string reason = object.at("reason"); + srb2::String public_key = object.at("public_key").get(); + srb2::String ip_address = object.at("ip_address").get(); + time_t expires = object.at("expires").get(); + UINT8 subnet_mask = object.at("subnet_mask").get(); + srb2::String username = object.at("username").get(); + srb2::String reason = object.at("reason").get(); if (!FromPrettyRRID(public_key_bin, public_key.c_str())) { @@ -86,16 +89,31 @@ void SV_LoadBans(void) if (!server) return; - json object; + JsonValue object; + + srb2::String banspath { srb2::format("{}/{}", srb2home, BANFILE) }; + srb2::io::BufferedInputStream bis; + try + { + srb2::io::FileStream fs { banspath, srb2::io::FileStreamMode::kRead }; + bis = srb2::io::BufferedInputStream(std::move(fs)); + } + catch (const srb2::io::FileStreamException& ex) + { + // file didn't open, likely doesn't exist + return; + } try { - std::ifstream f(va(pandf, srb2home, BANFILE)); - - if (f.is_open()) + srb2::Vector data = srb2::io::read_to_vec(bis); + srb2::String data_s; + data_s.reserve(data.size()); + for (auto b : data) { - f >> object; + data_s.push_back(std::to_integer(b)); } + object = JsonValue::from_json_string(data_s); } catch (const std::exception& ex) { @@ -115,11 +133,11 @@ void SV_LoadBans(void) { if (object.value("version", 1) == 1) { - json& array = object.at("bans"); + JsonValue& array = object.at("bans"); if (array.is_array()) { - load_bans_array_v1(array); + load_bans_array_v1(array.as_array()); } } } @@ -131,31 +149,36 @@ void SV_LoadBans(void) void SV_SaveBans(void) { - json object = json::object(); + JsonValue object = JsonValue(JsonObject()); object["version"] = 1; - json& array = object["bans"]; + JsonValue& array_value = object["bans"]; - array = json::array(); + array_value = JsonValue(JsonArray()); + JsonArray& array = array_value.as_array(); for (banrecord_t& ban : bans) { if (ban.deleted) continue; - array.push_back({ + array.push_back(JsonObject { {"public_key", GetPrettyRRID(ban.public_key, false)}, {"ip_address", SOCK_AddrToStr(ban.address)}, {"subnet_mask", ban.mask}, - {"expires", ban.expires}, + {"expires", static_cast(ban.expires)}, {"username", ban.username}, {"reason", ban.reason}, }); } + srb2::String json_string = object.to_json_string(); + srb2::String banfile_path = srb2::format("{}/{}", srb2home, BANFILE); + try { - std::ofstream(va(pandf, srb2home, BANFILE)) << object; + srb2::io::FileStream fs { banfile_path, srb2::io::FileStreamMode::kWrite }; + srb2::io::write_exact(fs, tcb::as_bytes(tcb::span(json_string))); } catch (const std::exception& ex) { @@ -345,10 +368,10 @@ static void SV_BanSearch(boolean remove) const char* stringaddress = SOCK_AddrToStr(ban.address); const char* stringkey = GetPrettyRRID(ban.public_key, true); - std::string recordprint = fmt::format( + srb2::String recordprint = srb2::format( "{}{} - {} [{}] - {}", stringaddress, - ban.mask && ban.mask != 32 ? fmt::format("/{}", ban.mask) : "", + ban.mask && ban.mask != 32 ? srb2::format("/{}", ban.mask) : "", ban.username, stringkey, ban.reason @@ -359,7 +382,7 @@ static void SV_BanSearch(boolean remove) if (ban.expires < now) recordprint += " - EXPIRED"; else - recordprint += fmt::format(" - expires {}m", (ban.expires - now)/60); + recordprint += srb2::format(" - expires {}m", (ban.expires - now)/60); } CONS_Printf("%s\n", recordprint.c_str()); diff --git a/src/k_credits.cpp b/src/k_credits.cpp index 401086b32..5b9023c19 100644 --- a/src/k_credits.cpp +++ b/src/k_credits.cpp @@ -12,11 +12,8 @@ #include "k_credits.h" -#include -#include #include #include -#include #include "doomdef.h" #include "doomstat.h" @@ -59,7 +56,9 @@ #include "r_main.h" #include "m_easing.h" -using nlohmann::json; +using srb2::JsonArray; +using srb2::JsonObject; +using srb2::JsonValue; enum credits_slide_types_e { @@ -75,14 +74,14 @@ enum credits_slide_types_e struct credits_slide_s { credits_slide_types_e type; - std::string label; - std::vector strings; + srb2::String label; + srb2::Vector strings; size_t strings_height; boolean play_demo_afterwards; int fade_out_music; }; -static std::vector g_credits_slides; +static srb2::Vector g_credits_slides; struct credits_star_s { @@ -94,10 +93,10 @@ struct credits_star_s static struct credits_s { size_t current_slide; - std::vector demo_maps; + srb2::Vector demo_maps; boolean skip; - std::vector stars; + srb2::Vector stars; fixed_t transition; fixed_t transition_prev; @@ -105,7 +104,7 @@ static struct credits_s tic_t animation_timer; - std::vector> split_slide_strings; + srb2::Vector> split_slide_strings; size_t split_slide_id; tic_t split_slide_delay; @@ -142,12 +141,14 @@ void F_LoadCreditsDefinitions(void) size_t credits_lump_len = W_LumpLength(credits_lump_id); const char *credits_lump = static_cast( W_CacheLumpNum(credits_lump_id, PU_CACHE) ); - json credits_array = json::parse(credits_lump, credits_lump + credits_lump_len); - if (credits_array.is_array() == false) + srb2::String json_string { credits_lump, credits_lump_len }; + JsonValue credits_parsed = JsonValue::from_json_string(json_string); + if (credits_parsed.is_array() == false) { I_Error("credits_def parse error: Not a JSON array"); return; } + JsonArray credits_array = credits_parsed.as_array(); if (credits_array.size() == 0) { @@ -156,11 +157,11 @@ void F_LoadCreditsDefinitions(void) try { - for (json& slide_obj : credits_array) + for (JsonValue& slide_obj : credits_array) { struct credits_slide_s slide; - std::string type_str = slide_obj.value("type", "scroll"); + srb2::String type_str = slide_obj.value("type", srb2::String("scroll")); if (type_str == "scroll") { @@ -196,18 +197,19 @@ void F_LoadCreditsDefinitions(void) throw std::runtime_error("unexpected type name '" + type_str + "'"); } - slide.label = slide_obj.value("label", ""); + slide.label = slide_obj.value("label", srb2::String("")); slide.strings_height = 0; if (slide_obj.contains("strings")) { - json strings_array = slide_obj.at("strings"); - if (strings_array.is_array() == true) + JsonValue strings_value = slide_obj.at("strings"); + if (strings_value.is_array() == true) { + JsonArray& strings_array = strings_value.as_array(); for (size_t i = 0; i < strings_array.size(); i++) { - slide.strings.push_back( strings_array.at(i) ); + slide.strings.push_back( strings_array.at(i).get() ); if (slide.type == CRED_TYPE_SCROLL) { @@ -289,7 +291,7 @@ static void F_InitCreditsSlide(void) size_t max_strings_per_screen = (num_strings - 1) / num_sub_screens + 1; size_t str_id = 0; - std::vector screen_strings; + srb2::Vector screen_strings; if (max_strings_per_screen == kMaxSlideStrings && num_strings % kMaxSlideStrings == 1) @@ -342,24 +344,24 @@ static void F_InitCreditsSlide(void) #endif ) { - slide->strings.push_back("#" + std::string(def->title)); + slide->strings.push_back("#" + srb2::String(def->title)); slide->strings_height += 12; if (def->author) { - slide->strings.push_back("by " + std::string(def->author)); + slide->strings.push_back("by " + srb2::String(def->author)); slide->strings_height += 12; } if (def->source) { - slide->strings.push_back("from " + std::string(def->source)); + slide->strings.push_back("from " + srb2::String(def->source)); slide->strings_height += 12; } if (def->composers) { - slide->strings.push_back("originally by " + std::string(def->composers)); + slide->strings.push_back("originally by " + srb2::String(def->composers)); slide->strings_height += 12; } @@ -478,7 +480,7 @@ void F_ContinueCredits(void) static UINT16 F_PickRandomCreditsDemoMap(void) { - std::vector allowedMaps; + srb2::Vector allowedMaps; for (INT32 i = 0; i < basenummapheaders; i++) // Only take from the base game. { @@ -911,7 +913,7 @@ static void F_DrawCreditsScroll(void) } else { - std::string new_str = str; + srb2::String new_str = str; if (new_str.at(0) == '*') { @@ -1017,7 +1019,7 @@ static void F_DrawCreditsSlide(void) return; } - const std::vector *slide_strings = &g_credits.split_slide_strings[ g_credits.split_slide_id ]; + const srb2::Vector *slide_strings = &g_credits.split_slide_strings[ g_credits.split_slide_id ]; const fixed_t strings_height = slide_strings->size() * 30 * FRACUNIT; fixed_t y = 0; @@ -1113,7 +1115,7 @@ static void F_DrawCreditsTyler52(void) V_DrawFadeScreen(0xFF00, 31 - (g_credits.tyler_fade * 4)); } - std::string memory_str = "In memory of"; + srb2::String memory_str = "In memory of"; const fixed_t memory_width = V_StringScaledWidth( FRACUNIT, FRACUNIT, FRACUNIT, 0, LSLOW_FONT, @@ -1126,7 +1128,7 @@ static void F_DrawCreditsTyler52(void) memory_str.c_str() ); - std::string tyler_str = "Tyler52"; + srb2::String tyler_str = "Tyler52"; const fixed_t tyler_width = V_StringScaledWidth( FRACUNIT, FRACUNIT, FRACUNIT, 0, LSHI_FONT, diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 8d9214b6a..10b7f8796 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -14,9 +14,10 @@ #include "k_dialogue.hpp" #include "k_dialogue.h" -#include #include +#include +#include "core/string.h" #include "info.h" #include "sounds.h" #include "g_game.h" @@ -43,7 +44,7 @@ void Dialogue::Typewriter::ClearText(void) textDest.clear(); } -void Dialogue::Typewriter::NewText(std::string newText) +void Dialogue::Typewriter::NewText(const srb2::String& newText) { text.clear(); @@ -174,7 +175,7 @@ void Dialogue::SetSpeaker(void) typewriter.voiceSfx = sfx_ktalk; } -void Dialogue::SetSpeaker(std::string skinName, int portraitID) +void Dialogue::SetSpeaker(srb2::String skinName, int portraitID) { Init(); @@ -215,7 +216,7 @@ void Dialogue::SetSpeaker(std::string skinName, int portraitID) } } -void Dialogue::SetSpeaker(std::string name, patch_t *patch, UINT8 *colormap, sfxenum_t voice) +void Dialogue::SetSpeaker(srb2::String name, patch_t *patch, UINT8 *colormap, sfxenum_t voice) { Init(); @@ -472,7 +473,7 @@ void Dialogue::Draw(void) .flags(V_VFLIP|V_FLIP) .patch(patchCache["TUTDIAGE"]); - std::string intertext = ""; + srb2::String intertext = ""; drawer .xy(10 - BASEVIDWIDTH, -3-32) @@ -486,7 +487,7 @@ void Dialogue::Draw(void) .patch(patchCache["TUTDIAG2"]); if (Held()) - intertext += ""; + intertext += ""; else intertext += ""; diff --git a/src/k_dialogue.hpp b/src/k_dialogue.hpp index d0a2a1578..678cc36b0 100644 --- a/src/k_dialogue.hpp +++ b/src/k_dialogue.hpp @@ -14,10 +14,10 @@ #ifndef __K_DIALOGUE_HPP__ #define __K_DIALOGUE_HPP__ -#include #include -#include +#include "core/hash_map.hpp" +#include "core/string.h" #include "doomdef.h" #include "doomtype.h" #include "typedef.h" @@ -33,8 +33,8 @@ public: static constexpr fixed_t kSlideSpeed = FRACUNIT / (TICRATE / 5); void SetSpeaker(void); - void SetSpeaker(std::string skinName, int portraitID); - void SetSpeaker(std::string name, patch_t *patch, UINT8 *colormap, sfxenum_t voice); + void SetSpeaker(srb2::String skinName, int portraitID); + void SetSpeaker(srb2::String name, patch_t *patch, UINT8 *colormap, sfxenum_t voice); void NewText(std::string_view newText); @@ -60,8 +60,8 @@ public: static constexpr fixed_t kTextSpeedDefault = FRACUNIT; static constexpr fixed_t kTextPunctPause = (FRACUNIT * TICRATE * 2) / 5; - std::string text; - std::string textDest; + srb2::String text; + srb2::String textDest; fixed_t textTimer; fixed_t textSpeed; @@ -71,7 +71,7 @@ public: sfxenum_t voiceSfx; bool syllable; - void NewText(std::string newText); + void NewText(const srb2::String& newText); void ClearText(void); void WriteText(void); @@ -86,11 +86,11 @@ private: patch_t *bgPatch; patch_t *confirmPatch; - std::string speaker; + srb2::String speaker; patch_t *portrait; UINT8 *portraitColormap; - std::unordered_map patchCache; + srb2::HashMap patchCache; bool active; fixed_t slide; diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 2410e904f..dfd24a6cc 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -11,11 +11,12 @@ #include #include -#include #include #include +#include "core/string.h" +#include "core/vector.hpp" #include "k_hud.h" #include "k_kart.h" #include "k_battle.h" @@ -6332,21 +6333,21 @@ typedef enum typedef struct { - std::string text; + srb2::String text; sfxenum_t sound; } message_t; struct messagestate_t { - std::deque messages; - std::string objective = ""; + std::deque messages; + srb2::String objective = ""; tic_t timer = 0; boolean persist = false; messagemode_t mode = MM_IN; const tic_t speedyswitch = 2*TICRATE; const tic_t lazyswitch = 4*TICRATE; - void add(std::string msg) + void add(srb2::String msg) { messages.push_back(msg); } @@ -6384,7 +6385,7 @@ struct messagestate_t switch (mode) { case MM_IN: - if (timer > messages[0].length()) + if (timer > messages[0].size()) switch_mode(MM_HOLD); break; case MM_HOLD: @@ -6394,7 +6395,7 @@ struct messagestate_t switch_mode(MM_OUT); break; case MM_OUT: - if (timer > messages[0].length()) + if (timer > messages[0].size()) next(); break; } diff --git a/src/k_podium.cpp b/src/k_podium.cpp index 29dd5ae4a..9d326d7aa 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -12,6 +12,7 @@ #include "k_podium.h" +#include "core/string.h" #include "doomdef.h" #include "d_main.h" #include "d_netcmd.h" @@ -51,8 +52,6 @@ #include "k_hud.h" -#include - typedef enum { PODIUM_ST_CONGRATS_SLIDEIN, @@ -652,7 +651,7 @@ void podiumData_s::Draw(void) } { - std::string emeraldName; + srb2::String emeraldName; if (emeraldNum > 7) { emeraldName = (useWhiteFrame ? "K_SUPER2" : "K_SUPER1"); diff --git a/src/k_profiles.cpp b/src/k_profiles.cpp index 1cededc39..8ccab2229 100644 --- a/src/k_profiles.cpp +++ b/src/k_profiles.cpp @@ -12,10 +12,13 @@ /// \brief implements methods for profiles etc. #include +#include #include #include +#include "core/string.h" +#include "core/vector.hpp" #include "io/streams.hpp" #include "doomtype.h" #include "d_main.h" // pandf @@ -253,7 +256,6 @@ void PR_InitNewProfile(void) void PR_SaveProfiles(void) { namespace fs = std::filesystem; - using json = nlohmann::json; using namespace srb2; namespace io = srb2::io; @@ -271,13 +273,13 @@ void PR_SaveProfiles(void) profile_t* cprof = profilesList[i]; jsonprof.version = PROFILEVER; - jsonprof.profilename = std::string(cprof->profilename); + jsonprof.profilename = String(cprof->profilename); std::copy(std::begin(cprof->public_key), std::end(cprof->public_key), std::begin(jsonprof.publickey)); std::copy(std::begin(cprof->secret_key), std::end(cprof->secret_key), std::begin(jsonprof.secretkey)); - jsonprof.playername = std::string(cprof->playername); - jsonprof.skinname = std::string(cprof->skinname); - jsonprof.colorname = std::string(skincolors[cprof->color].name); - jsonprof.followername = std::string(cprof->follower); + jsonprof.playername = String(cprof->playername); + jsonprof.skinname = String(cprof->skinname); + jsonprof.colorname = String(skincolors[cprof->color].name); + jsonprof.followername = String(cprof->follower); if (cprof->followercolor == FOLLOWERCOLOR_MATCH) { jsonprof.followercolorname = "Match"; @@ -292,11 +294,11 @@ void PR_SaveProfiles(void) } else if (cprof->followercolor >= numskincolors) { - jsonprof.followercolorname = std::string(); + jsonprof.followercolorname = String(); } else { - jsonprof.followercolorname = std::string(skincolors[cprof->followercolor].name); + jsonprof.followercolorname = String(skincolors[cprof->followercolor].name); } jsonprof.records.wins = cprof->wins; jsonprof.records.rounds = cprof->rounds; @@ -310,7 +312,7 @@ void PR_SaveProfiles(void) for (size_t j = 0; j < num_gamecontrols; j++) { - std::vector mappings; + srb2::Vector mappings; for (size_t k = 0; k < MAXINPUTMAPPING; k++) { mappings.push_back(cprof->controls[j][k]); @@ -321,16 +323,18 @@ void PR_SaveProfiles(void) ng.profiles.emplace_back(std::move(jsonprof)); } - std::vector ubjson = json::to_ubjson(ng); + JsonValue ngv; + to_json(ngv, ng); + Vector ubjson = ngv.to_ubjson(); - std::string realpath = fmt::format("{}/{}", srb2home, PROFILESFILE); - std::string bakpath = fmt::format("{}.bak", realpath); + String realpath = srb2::format("{}/{}", srb2home, PROFILESFILE); + String bakpath = srb2::format("{}.bak", realpath); - if (fs::exists(realpath)) + if (fs::exists(fs::path(static_cast(realpath)))) { try { - fs::rename(realpath, bakpath); + fs::rename(fs::path(static_cast(realpath)), fs::path(static_cast(bakpath))); } catch (const fs::filesystem_error& ex) { @@ -349,7 +353,7 @@ void PR_SaveProfiles(void) io::write(static_cast(0), file); // reserved2 io::write(static_cast(0), file); // reserved3 io::write(static_cast(0), file); // reserved4 - io::write_exact(file, tcb::as_bytes(tcb::make_span(ubjson))); + io::write_exact(file, ubjson); file.close(); } catch (const std::exception& ex) @@ -367,7 +371,6 @@ void PR_LoadProfiles(void) namespace fs = std::filesystem; using namespace srb2; namespace io = srb2::io; - using json = nlohmann::json; profile_t *dprofile = PR_MakeProfile( PROFILEDEFAULTNAME, @@ -378,7 +381,7 @@ void PR_LoadProfiles(void) true ); - std::string datapath {fmt::format("{}/{}", srb2home, PROFILESFILE)}; + String datapath { srb2::format("{}/{}", srb2home, PROFILESFILE) }; io::BufferedInputStream bis; try @@ -413,11 +416,10 @@ void PR_LoadProfiles(void) throw std::domain_error("Header is incompatible"); } - std::vector remainder = io::read_to_vec(bis); + Vector remainder = io::read_to_vec(bis); // safety: std::byte repr is always uint8_t 1-byte aligned - tcb::span remainder_as_u8 = tcb::span((uint8_t*)remainder.data(), remainder.size()); - json parsed = json::from_ubjson(remainder_as_u8); - js = parsed.template get(); + JsonValue parsed = JsonValue::from_ubjson(remainder); + from_json(parsed, js); } catch (const std::exception& ex) { diff --git a/src/k_profiles.h b/src/k_profiles.h index 9d9e1c3bd..4c71ee074 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -25,10 +25,10 @@ #include #include -#include -#include -#include +#include "core/json.hpp" +#include "core/string.h" +#include "core/vector.hpp" namespace srb2 { @@ -38,7 +38,7 @@ struct ProfileRecordsJson uint32_t wins; uint32_t rounds; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins, rounds) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins, rounds) }; struct ProfilePreferencesJson @@ -50,9 +50,8 @@ struct ProfilePreferencesJson bool autoring; bool rumble; uint8_t fov; - tm test; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( ProfilePreferencesJson, kickstartaccel, autoroulette, @@ -67,19 +66,19 @@ struct ProfilePreferencesJson struct ProfileJson { uint32_t version; - std::string profilename; - std::string playername; + String profilename; + String playername; std::array publickey = {{}}; std::array secretkey = {{}}; - std::string skinname; - std::string colorname; - std::string followername; - std::string followercolorname; + String skinname; + String colorname; + String followername; + String followercolorname; ProfileRecordsJson records; ProfilePreferencesJson preferences; - std::vector> controls = {}; + Vector> controls = {}; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( ProfileJson, version, profilename, @@ -98,9 +97,9 @@ struct ProfileJson struct ProfilesJson { - std::vector profiles; + Vector profiles; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfilesJson, profiles) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfilesJson, profiles) }; } // namespace srb2 diff --git a/src/k_waypoint.cpp b/src/k_waypoint.cpp index c7e6a8f35..a43fb1b1c 100644 --- a/src/k_waypoint.cpp +++ b/src/k_waypoint.cpp @@ -24,10 +24,12 @@ #include "cxxutil.hpp" #include -#include #include +#include "core/string.h" +#include "core/vector.hpp" + // The number of sparkles per waypoint connection in the waypoint visualisation static const UINT32 SPARKLES_PER_CONNECTION = 16U; @@ -2377,8 +2379,8 @@ static BlockItReturn_t K_TrackWaypointNearOffroad(line_t *line) struct complexity_sneaker_s { fixed_t bbox[4]; - //std::vector sectors; - //std::vector things; + //srb2::Vector sectors; + //srb2::Vector things; complexity_sneaker_s(sector_t *sec) { @@ -2631,7 +2633,7 @@ static INT32 K_CalculateTrackComplexity(void) delta = FixedMul(delta, FixedMul(FixedMul(dist_factor, radius_factor), wall_factor)); - std::string msg = fmt::format( + srb2::String msg = srb2::format( "TURN [{}]: r: {:.2f}, d: {:.2f}, w: {:.2f}, r*d*w: {:.2f}, DELTA: {}\n", turn_id, FixedToFloat(radius_factor), @@ -2644,7 +2646,7 @@ static INT32 K_CalculateTrackComplexity(void) trackcomplexity += (delta / FRACUNIT); } - std::vector sneaker_panels; + srb2::Vector sneaker_panels; for (size_t i = 0; i < numsectors; i++) { diff --git a/src/lua_profile.cpp b/src/lua_profile.cpp index 140833e40..0cbd704fc 100644 --- a/src/lua_profile.cpp +++ b/src/lua_profile.cpp @@ -10,10 +10,8 @@ #include #include -#include #include #include -#include #include @@ -21,6 +19,9 @@ extern "C" { #include "blua/lua.h" }; +#include "core/hash_map.hpp" +#include "core/string.h" +#include "core/vector.hpp" #include "v_draw.hpp" #include "command.h" @@ -58,7 +59,7 @@ struct lua_timer_t namespace { -std::unordered_map g_tic_timers; +srb2::HashMap g_tic_timers; }; // namespace @@ -156,7 +157,7 @@ void LUA_RenderTimers(void) return; } - std::vector view; + srb2::Vector view; view.reserve(g_tic_timers.size()); auto color_flag = [](double t) diff --git a/src/m_pw.cpp b/src/m_pw.cpp index 7a60d58fa..0808ff28c 100644 --- a/src/m_pw.cpp +++ b/src/m_pw.cpp @@ -14,13 +14,13 @@ #include #include #include -#include #include #include -#include #include "modp_b64/modp_b64.h" +#include "core/string.h" +#include "core/vector.hpp" #include "cxxutil.hpp" #include "command.h" @@ -43,12 +43,13 @@ namespace constexpr const UINT8 kRRSalt[17] = "0L4rlK}{9ay6'VJS"; -std::array decode_hash(std::string encoded) +std::array decode_hash(srb2::String encoded) { std::array decoded; - if (modp::b64_decode(encoded).size() != decoded.size()) + std::string encoded_stl { static_cast(encoded) }; + if (modp::b64_decode(encoded_stl).size() != decoded.size()) throw std::invalid_argument("hash is incorrectly sized"); - std::copy(encoded.begin(), encoded.end(), decoded.begin()); + std::copy(encoded_stl.begin(), encoded_stl.end(), decoded.begin()); return decoded; } @@ -60,7 +61,7 @@ struct Pw const std::array hash_; }; -std::vector passwords; +srb2::Vector passwords; // m_cond.c template @@ -629,8 +630,8 @@ try_password_e M_TryPassword(const char *password, boolean conditions) using var = std::variant; // Normalize input casing - std::string key = password; - strlwr(key.data()); + srb2::String key = password; + strlwr((char*)key.data()); UINT8 key_hash[M_PW_HASH_SIZE]; M_HashPassword(key_hash, key.c_str(), kRRSalt); @@ -684,8 +685,8 @@ try_password_e M_TryPassword(const char *password, boolean conditions) boolean M_TryExactPassword(const char *password, const char *encodedhash) { // Normalize input casing - std::string key = password; - strlwr(key.data()); + srb2::String key = password; + strlwr((char*)key.data()); UINT8 key_hash[M_PW_HASH_SIZE]; M_HashPassword(key_hash, key.c_str(), kRRSalt); diff --git a/src/media/avrecorder.hpp b/src/media/avrecorder.hpp index 2c3c9def7..bcf05ad7d 100644 --- a/src/media/avrecorder.hpp +++ b/src/media/avrecorder.hpp @@ -17,11 +17,11 @@ #include #include #include -#include -#include #include +#include "../core/string.h" +#include "../core/vector.hpp" #include "../audio/sample.hpp" namespace srb2::media @@ -49,7 +49,7 @@ public: int frame_rate; }; - std::string file_name; + srb2::String file_name; std::optional max_size; // file size limit std::optional> max_duration; @@ -63,7 +63,7 @@ public: { using instance_t = std::unique_ptr; - std::vector screen; + srb2::Vector screen; uint32_t width, height; int pts; diff --git a/src/media/avrecorder_feedback.cpp b/src/media/avrecorder_feedback.cpp index 38c016b89..bf0d6c4b5 100644 --- a/src/media/avrecorder_feedback.cpp +++ b/src/media/avrecorder_feedback.cpp @@ -10,10 +10,10 @@ #include #include -#include #include +#include "../core/string.h" #include "../cxxutil.hpp" #include "avrecorder_impl.hpp" @@ -39,7 +39,7 @@ void Impl::container_dtor_handler(const MediaContainer& container) const if (max_size_ && container.size() > *max_size_) { - const std::string line = fmt::format( + const srb2::String line = srb2::format( "Video size has exceeded limit {} > {} ({}%)." " This should not happen, please report this bug.\n", container.size(), @@ -100,7 +100,7 @@ void AVRecorder::draw_statistics() const { SRB2_ASSERT(impl_->video_encoder_ != nullptr); - auto draw = [](int x, std::string text, int32_t flags = 0) + auto draw = [](int x, const srb2::String text, int32_t flags = 0) { V_DrawThinString( x, @@ -144,7 +144,7 @@ void AVRecorder::draw_statistics() const return 0; }(); - draw(200, fmt::format("{:.0f}", fps), fps_color); - draw(230, fmt::format("{:.1f}s", impl_->container_->duration().count())); - draw(260, fmt::format("{:.1f} MB", size / kMb), mb_color); + draw(200, srb2::format("{:.0f}", fps), fps_color); + draw(230, srb2::format("{:.1f}s", impl_->container_->duration().count())); + draw(260, srb2::format("{:.1f} MB", size / kMb), mb_color); } diff --git a/src/media/avrecorder_impl.hpp b/src/media/avrecorder_impl.hpp index 0b7adbe70..8a96503fb 100644 --- a/src/media/avrecorder_impl.hpp +++ b/src/media/avrecorder_impl.hpp @@ -19,8 +19,8 @@ #include #include #include -#include +#include "../core/string.h" #include "../i_time.h" #include "avrecorder.hpp" #include "container.hpp" @@ -57,7 +57,7 @@ public: using frame_type = StagingVideoFrame::instance_t; }; - std::vector::frame_type> vec_; + srb2::Vector::frame_type> vec_; // This number only decrements once a frame has // actually been written to container. diff --git a/src/media/cfile.cpp b/src/media/cfile.cpp index ea2bcc434..5837edfc4 100644 --- a/src/media/cfile.cpp +++ b/src/media/cfile.cpp @@ -19,7 +19,7 @@ using namespace srb2::media; -CFile::CFile(const std::string file_name) : name_(file_name) +CFile::CFile(const srb2::String& file_name) : name_(file_name) { file_ = std::fopen(name(), "wb"); diff --git a/src/media/cfile.hpp b/src/media/cfile.hpp index 8dc455881..3f6e73dbd 100644 --- a/src/media/cfile.hpp +++ b/src/media/cfile.hpp @@ -12,7 +12,8 @@ #define __SRB2_MEDIA_CFILE_HPP__ #include -#include + +#include "../core/string.h" namespace srb2::media { @@ -20,7 +21,7 @@ namespace srb2::media class CFile { public: - CFile(const std::string file_name); + CFile(const srb2::String& file_name); ~CFile(); operator std::FILE*() const { return file_; } @@ -28,7 +29,7 @@ public: const char* name() const { return name_.c_str(); } private: - std::string name_; + srb2::String name_; std::FILE* file_; }; diff --git a/src/media/container.hpp b/src/media/container.hpp index c391f4379..836f2e47c 100644 --- a/src/media/container.hpp +++ b/src/media/container.hpp @@ -14,8 +14,8 @@ #include #include #include -#include +#include "../core/string.h" #include "audio_encoder.hpp" #include "video_encoder.hpp" @@ -30,7 +30,7 @@ public: struct Config { - std::string file_name; + srb2::String file_name; dtor_cb_t destructor_callback; }; diff --git a/src/media/options.hpp b/src/media/options.hpp index a97cb4ef1..e17d7ea44 100644 --- a/src/media/options.hpp +++ b/src/media/options.hpp @@ -13,11 +13,11 @@ #include #include -#include #include -#include -#include +#include "../core/hash_map.hpp" +#include "../core/string.h" +#include "../core/vector.hpp" #include "../command.h" namespace srb2::media @@ -26,7 +26,7 @@ namespace srb2::media class Options { public: - using map_t = std::unordered_map; + using map_t = srb2::HashMap; template struct Range @@ -46,7 +46,7 @@ public: static consvar_t values(const char* default_value, const Range range, std::map list = {}); private: - static std::vector cvars_; + static srb2::Vector cvars_; const char* prefix_; map_t map_; @@ -54,6 +54,11 @@ private: const consvar_t& cvar(const char* option) const; }; +// clang-format off +extern template consvar_t Options::values(const char* default_value, const Range range, std::map list); +extern template consvar_t Options::values(const char* default_value, const Range range, std::map list); +// clang-format on + }; // namespace srb2::media #endif // __SRB2_MEDIA_OPTIONS_HPP__ diff --git a/src/media/options_values.cpp b/src/media/options_values.cpp index 2f648c264..66fb9e713 100644 --- a/src/media/options_values.cpp +++ b/src/media/options_values.cpp @@ -12,6 +12,7 @@ #include +#include "../core/vector.hpp" #include "options.hpp" #include "vorbis.hpp" #include "vp8.hpp" @@ -23,7 +24,7 @@ using namespace srb2::media; // to be defined in the same translation unit as // Options::cvars_ to guarantee initialization order. -std::vector Options::cvars_; +srb2::Vector Options::cvars_; // clang-format off const Options VorbisEncoder::options_("vorbis", { diff --git a/src/media/vorbis_error.hpp b/src/media/vorbis_error.hpp index cbd3bffe4..27940080c 100644 --- a/src/media/vorbis_error.hpp +++ b/src/media/vorbis_error.hpp @@ -11,11 +11,11 @@ #ifndef __SRB2_MEDIA_VORBIS_ERROR_HPP__ #define __SRB2_MEDIA_VORBIS_ERROR_HPP__ -#include - #include #include +#include "../core/string.h" + class VorbisError { public: @@ -23,7 +23,7 @@ public: operator int() const { return error_; } - std::string name() const + srb2::String name() const { switch (error_) { @@ -43,12 +43,12 @@ private: }; template <> -struct fmt::formatter : formatter +struct fmt::formatter : formatter { template auto format(const VorbisError& error, FormatContext& ctx) const { - return formatter::format(error.name(), ctx); + return formatter::format(error.name(), ctx); } }; diff --git a/src/media/vpx_error.hpp b/src/media/vpx_error.hpp index ec53945e2..a5625f9da 100644 --- a/src/media/vpx_error.hpp +++ b/src/media/vpx_error.hpp @@ -11,22 +11,22 @@ #ifndef __SRB2_MEDIA_VPX_ERROR_HPP__ #define __SRB2_MEDIA_VPX_ERROR_HPP__ -#include - #include #include +#include "../core/string.h" + class VpxError { public: VpxError(vpx_codec_ctx_t& ctx) : ctx_(&ctx) {} - std::string description() const + srb2::String description() const { const char* error = vpx_codec_error(ctx_); const char* detail = vpx_codec_error_detail(ctx_); - return detail ? fmt::format("{}: {}", error, detail) : error; + return detail ? srb2::format("{}: {}", error, detail) : error; } private: @@ -34,12 +34,12 @@ private: }; template <> -struct fmt::formatter : formatter +struct fmt::formatter : formatter { template auto format(const VpxError& error, FormatContext& ctx) const { - return formatter::format(error.description(), ctx); + return formatter::format(error.description(), ctx); } }; diff --git a/src/media/webm_container.hpp b/src/media/webm_container.hpp index 3b82b3b9b..ed974bb16 100644 --- a/src/media/webm_container.hpp +++ b/src/media/webm_container.hpp @@ -13,11 +13,11 @@ #include #include -#include #include #include +#include "../core/hash_map.hpp" #include "container.hpp" #include "webm.hpp" #include "webm_writer.hpp" @@ -88,7 +88,7 @@ private: mutable std::recursive_mutex queue_mutex_; - std::unordered_map queue_; + srb2::HashMap queue_; webm::timestamp latest_timestamp_ = 0; std::size_t queue_size_ = 0; diff --git a/src/media/webm_writer.hpp b/src/media/webm_writer.hpp index 6ebd998ce..c4b2fd60f 100644 --- a/src/media/webm_writer.hpp +++ b/src/media/webm_writer.hpp @@ -12,10 +12,10 @@ #define __SRB2_MEDIA_WEBM_WRITER_HPP__ #include -#include #include +#include "../core/string.h" #include "cfile.hpp" namespace srb2::media @@ -24,7 +24,7 @@ namespace srb2::media class WebmWriter : public CFile, public mkvmuxer::MkvWriter { public: - WebmWriter(const std::string file_name) : CFile(file_name), MkvWriter(static_cast(*this)) {} + WebmWriter(const srb2::String& file_name) : CFile(file_name), MkvWriter(static_cast(*this)) {} ~WebmWriter() { MkvWriter::Close(); } }; diff --git a/src/media/yuv420p.hpp b/src/media/yuv420p.hpp index cb4648ad5..d430a27bd 100644 --- a/src/media/yuv420p.hpp +++ b/src/media/yuv420p.hpp @@ -12,8 +12,8 @@ #define __SRB2_MEDIA_YUV420P_HPP__ #include -#include +#include "../core/vector.hpp" #include "video_frame.hpp" namespace srb2::media @@ -40,7 +40,7 @@ public: int width_ = 0; int height_ = 0; - std::vector vec_; + srb2::Vector vec_; }; YUV420pFrame(int pts, Buffer y, Buffer u, Buffer v, const BufferRGBA& rgba); diff --git a/src/menus/class-egg-tv/EggTV.cpp b/src/menus/class-egg-tv/EggTV.cpp index 346f2edbf..1fc51337f 100644 --- a/src/menus/class-egg-tv/EggTV.cpp +++ b/src/menus/class-egg-tv/EggTV.cpp @@ -11,11 +11,11 @@ #include #include #include -#include #include #include +#include "../../core/string.h" #include "../../cxxutil.hpp" #include "../../v_draw.hpp" #include "EggTV.hpp" @@ -85,11 +85,11 @@ void draw_face(const Draw& draw, const EggTVData::Replay::Standing& player, face } } -std::string player_time_string(const EggTVData::Replay::Standing& player) +srb2::String player_time_string(const EggTVData::Replay::Standing& player) { if (player.time) { - return fmt::format( + return srb2::format( R"({}'{}"{})", G_TicsToMinutes(*player.time, true), G_TicsToSeconds(*player.time), @@ -102,7 +102,7 @@ std::string player_time_string(const EggTVData::Replay::Standing& player) } } -std::string player_points_string(const EggTVData::Replay::Standing& player) +srb2::String player_points_string(const EggTVData::Replay::Standing& player) { return player.score ? fmt::format("{} PTS", *player.score) : "NO CONTEST"; } diff --git a/src/menus/class-egg-tv/EggTV.hpp b/src/menus/class-egg-tv/EggTV.hpp index 5e346ecde..81f0d0906 100644 --- a/src/menus/class-egg-tv/EggTV.hpp +++ b/src/menus/class-egg-tv/EggTV.hpp @@ -19,6 +19,7 @@ #include "EggTVData.hpp" #include "EggTVGraphics.hpp" +#include "../../core/vector.hpp" #include "../../doomdef.h" // TICRATE #include "../../i_time.h" #include "../../k_menu.h" @@ -197,7 +198,7 @@ private: { public: using limiter_t = std::function; - using anims_t = std::vector; + using anims_t = srb2::Vector; explicit Cursor(anims_t anims, limiter_t limiter) : limiter_(limiter), anims_(anims) {} diff --git a/src/menus/class-egg-tv/EggTVData.cpp b/src/menus/class-egg-tv/EggTVData.cpp index a4ea461c2..4fbe93424 100644 --- a/src/menus/class-egg-tv/EggTVData.cpp +++ b/src/menus/class-egg-tv/EggTVData.cpp @@ -14,13 +14,14 @@ #include #include #include -#include #include #include #include // std::filesystem::path formatter -#include +#include "../../core/string.h" +#include "../../core/json.hpp" +#include "../../io/streams.hpp" #include "../../cxxutil.hpp" #include "EggTVData.hpp" @@ -34,15 +35,15 @@ using namespace srb2::menus::egg_tv; namespace fs = std::filesystem; -using nlohmann::json; +using srb2::JsonValue; template <> -struct fmt::formatter : formatter +struct fmt::formatter : formatter { template auto format(const fs::filesystem_error& ex, FormatContext& ctx) const { - return formatter::format( + return formatter::format( fmt::format("{}, path1={}, path2={}", ex.what(), ex.path1(), ex.path2()), ctx ); @@ -65,13 +66,13 @@ To time_point_conv(From time) return std::chrono::time_point_cast(To::clock::now() + (time - From::clock::now())); } -json& ensure_array(json& object, const char* key) +JsonValue& ensure_array(JsonValue& object, const char* key) { - json& array = object[key]; + JsonValue& array = object[key]; if (!array.is_array()) { - array = json::array(); + array = JsonValue::array(); } return array; @@ -91,18 +92,16 @@ EggTVData::EggTVData() : favorites_(ensure_array(favoritesFile_, "favorites")) } } -json EggTVData::cache_favorites() const +JsonValue EggTVData::cache_favorites() const { - json object; + JsonValue object; try { - std::ifstream f(favoritesPath_); - - if (f.is_open()) - { - f >> object; - } + srb2::io::FileStream stream { favoritesPath_.generic_string() }; + srb2::Vector f = srb2::io::read_to_vec(stream); + srb2::String json_string { (const char*)f.data(), f.size() }; + object = JsonValue::from_json_string(json_string); } catch (const std::exception& ex) { @@ -199,9 +198,9 @@ EggTVData::Folder::Folder(EggTVData& tv, const fs::directory_entry& entry) : } } -EggTVData::Replay::Title::operator const std::string() const +EggTVData::Replay::Title::operator const srb2::String() const { - return second().empty() ? first() : fmt::format("{} - {}", first(), second()); + return second().empty() ? first() : srb2::format("{} - {}", first(), second()); } EggTVData::Replay::Replay(Folder::Cache::ReplayRef& ref) : ref_(&ref) @@ -230,7 +229,7 @@ EggTVData::Replay::Replay(Folder::Cache::ReplayRef& ref) : ref_(&ref) const std::string_view str = info.title; const std::size_t mid = str.find(kDelimiter); - title_ = Title(str.substr(0, mid), mid == std::string::npos ? "" : str.substr(mid + kDelimiter.size())); + title_ = Title(str.substr(0, mid), mid == srb2::String::npos ? "" : str.substr(mid + kDelimiter.size())); //title_ = Title("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW", "WWWWWWWWWWWWWWWWWWWWWWWWWWW"); } @@ -316,13 +315,13 @@ void EggTVData::Replay::toggle_favorite() const { const auto& it = ref_->iterator_to_favorite(); - if (it != ref_->favorites().end()) + if (it != ref_->favorites().as_array().end()) { - ref_->favorites().erase(it); + ref_->favorites().as_array().erase(it); } else { - ref_->favorites().emplace_back(ref_->favorites_path()); + ref_->favorites().as_array().emplace_back(ref_->favorites_path()); } ref_->cache().folder().tv().save_favorites(); @@ -382,7 +381,9 @@ void EggTVData::save_favorites() const { try { - std::ofstream(favoritesPath_) << favoritesFile_; + srb2::String json_string = favoritesFile_.to_json_string(); + srb2::io::FileStream fs { favoritesPath_.generic_string(), srb2::io::FileStreamMode::kWrite }; + srb2::io::write_exact(fs, tcb::as_bytes(tcb::span(json_string.data(), json_string.size()))); } catch (const std::exception& ex) { diff --git a/src/menus/class-egg-tv/EggTVData.hpp b/src/menus/class-egg-tv/EggTVData.hpp index 4661b95e6..09ab45b93 100644 --- a/src/menus/class-egg-tv/EggTVData.hpp +++ b/src/menus/class-egg-tv/EggTVData.hpp @@ -17,14 +17,13 @@ #include #include #include -#include #include #include #include #include -#include - +#include "../../core/string.h" +#include "../../core/json.hpp" #include "../../cxxutil.hpp" #include "../../d_main.h" // srb2home @@ -40,10 +39,10 @@ private: const std::filesystem::path root_ = std::filesystem::path{srb2home} / "media/replay/online"; const std::filesystem::path favoritesPath_ = root_ / "favorites.json"; - nlohmann::json favoritesFile_ = cache_favorites(); - nlohmann::json& favorites_; + JsonValue favoritesFile_ = cache_favorites(); + JsonValue& favorites_; - nlohmann::json cache_favorites() const; + JsonValue cache_favorites() const; void cache_folders(); void save_favorites() const; @@ -91,20 +90,21 @@ public: released_ = true; } - bool favorited() const { return iterator_to_favorite() != favorites().end(); } - nlohmann::json& favorites() const { return cache().folder().tv().favorites_; } + bool favorited() const { return iterator_to_favorite() != favorites().as_array().end(); } + JsonValue& favorites() const { return cache().folder().tv().favorites_; } - std::string favorites_path() const + srb2::String favorites_path() const { // path::generic_string converts to forward // slashes on Windows. This should suffice to make // the JSON file portable across installations. - return (std::filesystem::path{cache().folder().name()} / filename()).generic_string(); + return (std::filesystem::path{std::string_view(cache().folder().name())} / filename()).generic_string(); } - nlohmann::json::const_iterator iterator_to_favorite() const + JsonArray::const_iterator iterator_to_favorite() const { - return std::find(favorites().begin(), favorites().end(), favorites_path()); + srb2::String path = favorites_path(); + return std::find(favorites().as_array().begin(), favorites().as_array().end(), static_cast(path)); } private: @@ -134,13 +134,13 @@ public: int y = 0; bool empty() { return size() == 0; } - std::filesystem::path path() const { return tv_->root_ / name_; } + std::filesystem::path path() const { return tv_->root_ / std::string_view(name_); } EggTVData& tv() const { return *tv_; } std::size_t size() const { return size_; } const time_point_t& time() const { return time_; } - const std::string& name() const { return name_; } + const srb2::String& name() const { return name_; } std::unique_ptr load() { return std::make_unique(*this); }; @@ -150,7 +150,7 @@ public: std::size_t size_; time_point_t time_; EggTVData* tv_; - std::string name_; + srb2::String name_; }; class Replay @@ -165,18 +165,18 @@ public: { } - const std::string& first() const { return first_; } - const std::string& second() const { return second_; } + const srb2::String& first() const { return first_; } + const srb2::String& second() const { return second_; } - operator const std::string() const; + operator const srb2::String() const; private: - std::string first_, second_; + srb2::String first_, second_; }; struct Standing { - std::string name; + srb2::String name; std::optional skin; std::size_t color; std::optional time; @@ -247,7 +247,7 @@ public: void toggle_favorite() const; bool invalid() const { return invalid_; } - bool favorited() const { return ref_->iterator_to_favorite() != ref_->favorites().end(); } + bool favorited() const { return ref_->iterator_to_favorite() != ref_->favorites().as_array().end(); } std::filesystem::path path() const { return ref_->cache().folder().path() / ref_->filename(); } const time_point_t& date() const { return ref_->time(); } diff --git a/src/menus/class-egg-tv/EggTVGraphics.hpp b/src/menus/class-egg-tv/EggTVGraphics.hpp index 280a203a9..d1928a0ae 100644 --- a/src/menus/class-egg-tv/EggTVGraphics.hpp +++ b/src/menus/class-egg-tv/EggTVGraphics.hpp @@ -13,8 +13,8 @@ #include #include -#include +#include "../../core/hash_map.hpp" #include "../../doomdef.h" // skincolornum_t #include "../../v_draw.hpp" @@ -90,7 +90,7 @@ public: patch select = "RHTVSQSL"; - std::unordered_map gametype = { + srb2::HashMap gametype = { {"Race", "RHGT1"}, {"Battle", "RHGT2"}, {"Prison Break", "RHGT3"}, diff --git a/src/menus/extras-statistics.cpp b/src/menus/extras-statistics.cpp index 5ae8832f0..efea7af67 100644 --- a/src/menus/extras-statistics.cpp +++ b/src/menus/extras-statistics.cpp @@ -11,6 +11,8 @@ /// \file menus/extras-challenges.c /// \brief Statistics menu +#include + #include "../k_menu.h" #include "../z_zone.h" #include "../m_cond.h" // Condition Sets @@ -260,7 +262,7 @@ static void M_StatisticsPageInit(void) M_StatisticsChars(); break; } - + case statisticspage_gp: { M_StatisticsGP(); diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 52c936bd6..5539ed5a2 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -29,6 +29,8 @@ #include +#include "../core/string.h" + static void M_GonerDrawer(void); static void M_GonerConclude(INT32 choice); static boolean M_GonerInputs(INT32 ch); @@ -46,7 +48,7 @@ menuitem_t MAIN_Goner[] = {.routine = M_VideoOptions}, 0, 0}, {IT_STRING | IT_CALL, "SOUND OPTIONS", - "CALIBRATE AURAL DATASTREAM.", NULL, + "CALIBRATE AURAL DATASTREAM.", NULL, {.routine = M_SoundOptions}, 0, 0}, {IT_STRING | IT_CALL, "PROFILE SETUP", @@ -95,7 +97,7 @@ class GonerSpeaker public: float offset = 0; - GonerSpeaker(std::string skinName, float offset) + GonerSpeaker(const srb2::String& skinName, float offset) { if (!skinName.empty()) { @@ -144,11 +146,11 @@ class GonerChatLine { public: gonerspeakers_t speaker; - std::string dialogue; + srb2::String dialogue; int value; // Mutlipurpose. void (*routine)(void); - GonerChatLine(gonerspeakers_t speaker, int delay, std::string dialogue) + GonerChatLine(gonerspeakers_t speaker, int delay, const srb2::String& dialogue) { char *newText = V_ScaledWordWrap( (BASEVIDWIDTH/2 + 6) << FRACBITS, @@ -158,7 +160,7 @@ public: ); this->speaker = speaker; - this->dialogue = std::string(newText); + this->dialogue = srb2::String(newText); this->value = delay; this->routine = nullptr; @@ -1142,7 +1144,7 @@ static void M_GonerDrawer(void) for (auto & element : LinesOutput) { - std::string text; + srb2::String text; INT32 flags; if (newy < 0) break; diff --git a/src/music.cpp b/src/music.cpp index 44f1edb3c..b50dfaf66 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -174,7 +174,7 @@ void Music_Init(void) { Tune& tune = g_tunes.insert("credits"); - tune.priority = 100; + tune.priority = 101; tune.song = "_creds"; tune.credit = true; } @@ -182,7 +182,7 @@ void Music_Init(void) { Tune& tune = g_tunes.insert("shore"); - tune.priority = 100; + tune.priority = 101; tune.loop = false; } diff --git a/src/music_manager.cpp b/src/music_manager.cpp index b93745a88..86ca4bec9 100644 --- a/src/music_manager.cpp +++ b/src/music_manager.cpp @@ -14,13 +14,13 @@ #include #include #include -#include #include #include "music_manager.hpp" #include "music_tune.hpp" +#include "core/string.h" #include "d_clisrv.h" #include "doomtype.h" #include "i_sound.h" @@ -49,8 +49,8 @@ void TuneManager::tick() Tune* tune = current_tune(); - std::string old_song = current_song_; - current_song_ = tune && tune->playing() && !tune->suspend ? tune->song : std::string{}; + srb2::String old_song = current_song_; + current_song_ = tune && tune->playing() && !tune->suspend ? tune->song : srb2::String{}; bool changed = current_song_ != old_song; @@ -167,7 +167,8 @@ void TuneManager::pause_unpause() const bool TuneManager::load() const { - lumpnum_t lumpnum = W_CheckNumForLongName(fmt::format("O_{}", current_song_).c_str()); + srb2::String lumpstring = srb2::format("O_{}", current_song_); + lumpnum_t lumpnum = W_CheckNumForLongName(lumpstring.c_str()); if (lumpnum == LUMPERROR) { diff --git a/src/music_manager.hpp b/src/music_manager.hpp index adddd3c48..e3d377258 100644 --- a/src/music_manager.hpp +++ b/src/music_manager.hpp @@ -11,11 +11,10 @@ #ifndef MUSIC_MANAGER_HPP #define MUSIC_MANAGER_HPP -#include #include -#include -#include +#include "core/hash_map.hpp" +#include "core/string.h" #include "cxxutil.hpp" #include "music_tune.hpp" @@ -25,7 +24,7 @@ namespace srb2::music class TuneManager { public: - const std::string& current_song() const { return current_song_; } + const srb2::String& current_song() const { return current_song_; } Tune* current_tune() const { @@ -99,8 +98,8 @@ public: } private: - std::unordered_map map_; - std::string current_song_; + srb2::HashMap map_; + srb2::String current_song_; tic_t time_sync_; tic_t time_local_; @@ -113,11 +112,20 @@ private: decltype(map_)::const_iterator current_iterator() const { - return std::max_element( - map_.begin(), - map_.end(), - [](const auto& a, const auto& b) { return a.second < b.second; } - ); + // Not using std::max_element due to buggy clang libc++ LegacyInputIterator assertion + auto first = map_.begin(); + auto last = map_.end(); + if (first == last) return first; + + auto max = first; + while (++first != last) + { + if (max->second < first->second) + { + max = first; + } + } + return max; } bool load() const; diff --git a/src/music_tune.hpp b/src/music_tune.hpp index 2f9c925f7..557eb79a9 100644 --- a/src/music_tune.hpp +++ b/src/music_tune.hpp @@ -14,8 +14,8 @@ #include #include #include -#include +#include "core/string.h" #include "doomdef.h" #include "doomtype.h" #include "k_boss.h" @@ -29,7 +29,7 @@ class Tune public: explicit Tune() {} - std::string song; // looks up the lump + srb2::String song; // looks up the lump // Higher priority tunes play first. int priority = 0; diff --git a/src/objects/ballhog.cpp b/src/objects/ballhog.cpp index a3661fb69..1fe2ba218 100644 --- a/src/objects/ballhog.cpp +++ b/src/objects/ballhog.cpp @@ -10,6 +10,8 @@ /// \file ballhog.cpp /// \brief Ballhog item code. +#include + #include "../doomdef.h" #include "../doomstat.h" #include "../info.h" diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 694d0564c..8ceba4df4 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -9,13 +9,13 @@ //----------------------------------------------------------------------------- #include -#include -#include #include #include "../mobj_list.hpp" +#include "../core/hash_map.hpp" +#include "../core/vector.hpp" #include "../doomdef.h" #include "../doomtype.h" #include "../info.h" @@ -224,7 +224,7 @@ struct Checkpoint : mobj_t speed(speed() - FixedDiv(speed() / 50, max(speed_multiplier(), 1))); } } - + if (!top_half_has_passed()) { sparkle_between(0); @@ -506,7 +506,7 @@ struct CheckpointManager auto count() { return list_.count(); } - const std::vector* lines_for(const Checkpoint* chk) const + const srb2::Vector* lines_for(const Checkpoint* chk) const { auto it = lines_.find(chk->linetag()); return it != lines_.end() ? &it->second : nullptr; @@ -514,11 +514,11 @@ struct CheckpointManager private: srb2::MobjList list_; - std::unordered_map> lines_; + srb2::HashMap> lines_; - static std::vector tagged_lines(INT32 tag) + static srb2::Vector tagged_lines(INT32 tag) { - std::vector checklines; + srb2::Vector checklines; INT32 li; TAG_ITER_LINES(tag, li) { @@ -573,18 +573,18 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe } LineOnDemand* gate; - const std::vector* lines = g_checkpoints.lines_for(chk); + const srb2::Vector* lines = g_checkpoints.lines_for(chk); if (!lines || lines->empty()) { LineOnDemand dyngate = chk->crossing_line(); if (!ray.overlaps(dyngate)) return false; - gate = &dyngate; + gate = &dyngate; } - else + else { - auto it = find_if( + auto it = std::find_if( lines->begin(), lines->end(), [&](const line_t* line) @@ -592,7 +592,7 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe return ray.overlaps(*line); } ); - + if (it == lines->end()) { return false; @@ -615,7 +615,7 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe { // Did not cross. return false; - + } return true; diff --git a/src/p_setup.cpp b/src/p_setup.cpp index a0fb9e421..42cd68ad3 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -13,8 +13,6 @@ /// \brief Do all the WAD I/O, get map description, set up initial state and misc. LUTs #include -#include -#include #include @@ -96,6 +94,8 @@ #include "taglist.h" // SRB2Kart +#include "core/string.h" +#include "core/vector.hpp" #include "k_kart.h" #include "k_race.h" #include "k_battle.h" // K_BattleInit @@ -7854,7 +7854,7 @@ static void P_LoadRecordGhosts(void) map(cv_ghost_last, value, kLast); }; - auto add_ghosts = [gpath](const std::string& base, UINT8 bits) + auto add_ghosts = [gpath](const srb2::String& base, UINT8 bits) { auto load = [base](const char* suffix) { P_TryAddExternalGhost(fmt::format("{}-{}.lmp", base, suffix).c_str()); }; @@ -7991,7 +7991,7 @@ static void P_ShuffleTeams(void) CONS_Debug(DBG_TEAMS, "Shuffling player teams...\n"); - std::vector player_shuffle; + srb2::Vector player_shuffle; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] == false || players[i].spectator == true) @@ -9135,7 +9135,7 @@ static tic_t round_to_next_second(tic_t time) static void P_DeriveAutoMedalTimes(mapheader_t& map) { // Gather staff ghost times - std::vector stafftimes; + srb2::Vector stafftimes; for (int i = 0; i < map.ghostCount; i++) { tic_t time = map.ghostBrief[i]->time; @@ -9325,7 +9325,7 @@ UINT8 P_InitMapData(void) { continue; } - std::string ghostdirname = fmt::format("staffghosts/{}/", mapheaderinfo[i]->lumpname); + srb2::String ghostdirname = srb2::format("staffghosts/{}/", mapheaderinfo[i]->lumpname); UINT16 lumpstart = W_CheckNumForFolderStartPK3(ghostdirname.c_str(), wadindex, 0); if (lumpstart == INT16_MAX) diff --git a/src/r_debug_printer.cpp b/src/r_debug_printer.cpp index a6b2e7749..d19569dcf 100644 --- a/src/r_debug_printer.cpp +++ b/src/r_debug_printer.cpp @@ -9,12 +9,12 @@ //----------------------------------------------------------------------------- #include -#include -#include #include "r_debug.hpp" #include "v_draw.hpp" +#include "core/hash_map.hpp" +#include "core/hash_set.hpp" #include "doomdef.h" #include "doomtype.h" #include "r_textures.h" @@ -25,7 +25,7 @@ using srb2::Draw; namespace { -std::unordered_set frame_list; +srb2::HashSet frame_list; }; // namespace diff --git a/src/r_plane.cpp b/src/r_plane.cpp index ab6659809..36f9b6b03 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -14,6 +14,8 @@ /// while maintaining a per column clipping list only. /// Moreover, the sky areas have to be determined. +#include + #include #include "command.h" diff --git a/src/r_textures_dups.cpp b/src/r_textures_dups.cpp index 90bb48dc5..eed8b2872 100644 --- a/src/r_textures_dups.cpp +++ b/src/r_textures_dups.cpp @@ -13,14 +13,14 @@ #include #include #include -#include #include #include -#include -#include #include +#include "core/hash_map.hpp" +#include "core/string.h" +#include "core/vector.hpp" #include "cxxutil.hpp" #include "doomstat.h" #include "r_textures.h" @@ -29,14 +29,14 @@ namespace { -std::unordered_map> g_dups; -std::map> g_warnings; +srb2::HashMap> g_dups; +std::map> g_warnings; std::thread g_dups_thread; -std::string key8char(const char cstr[8]) +srb2::String key8char(const char cstr[8]) { std::string_view view(cstr, 8); - std::string key; + srb2::String key; view = view.substr(0, view.find('\0')); // terminate by '\0' key.reserve(view.size()); @@ -50,7 +50,7 @@ std::string key8char(const char cstr[8]) return key; } -std::string texture_location(const texture_t& tex) +srb2::String texture_location(const texture_t& tex) { if (tex.type == TEXTURETYPE_SINGLEPATCH) { @@ -59,7 +59,7 @@ std::string texture_location(const texture_t& tex) const texpatch_t& texpat = tex.patches[0]; const wadfile_t& wad = *wadfiles[texpat.wad]; - return fmt::format( + return srb2::format( "'{}/{}'", std::filesystem::path(wad.filename).filename().string(), wad.lumpinfo[texpat.lump].fullname @@ -108,7 +108,7 @@ void R_CheckTextureDuplicates(INT32 start, INT32 end) auto collate_dups = [end, find_dup](int32_t start) { const texture_t* t1 = textures[start]; - const std::string key = key8char(t1->name); + const srb2::String key = key8char(t1->name); if (g_dups.find(key) != g_dups.end()) { @@ -119,7 +119,7 @@ void R_CheckTextureDuplicates(INT32 start, INT32 end) if (idx < end) { - std::vector& v = g_dups[key]; + srb2::Vector& v = g_dups[key]; v.push_back(textures[start]); @@ -175,7 +175,7 @@ void R_PrintTextureWarnings(void) { CONS_Alert(CONS_WARNING, "\n%s", header.c_str()); - for (const std::string& warning : v) + for (const srb2::String& warning : v) { CONS_Printf("%s\n", warning.c_str()); } diff --git a/src/rhi/gl2/gl2_rhi.cpp b/src/rhi/gl2/gl2_rhi.cpp index 0097dea20..cf35eebb5 100644 --- a/src/rhi/gl2/gl2_rhi.cpp +++ b/src/rhi/gl2/gl2_rhi.cpp @@ -14,13 +14,13 @@ #include #include #include -#include #include #include #include #include +#include "../../core/vector.hpp" #include "../shader_load_context.hpp" using namespace srb2; @@ -840,8 +840,8 @@ rhi::Handle Gl2Rhi::create_program(const ProgramDesc& desc) // breaks the AMD driver's program linker in a bizarre way. // Process shader sources - std::vector vert_sources; - std::vector frag_sources; + srb2::Vector vert_sources; + srb2::Vector frag_sources; ShaderLoadContext vert_ctx; ShaderLoadContext frag_ctx; vert_ctx.set_version("120"); @@ -873,11 +873,11 @@ rhi::Handle Gl2Rhi::create_program(const ProgramDesc& desc) { GLint max_length = 0; gl_->GetShaderiv(program.vertex_shader, GL_INFO_LOG_LENGTH, &max_length); - std::vector compile_error(max_length); + srb2::Vector compile_error(max_length); gl_->GetShaderInfoLog(program.vertex_shader, max_length, &max_length, compile_error.data()); gl_->DeleteShader(program.vertex_shader); - throw std::runtime_error(fmt::format("Vertex shader compilation failed: {}", std::string(compile_error.data()))); + throw std::runtime_error(fmt::format("Vertex shader compilation failed: {}", String(compile_error.data()))); } program.fragment_shader = gl_->CreateShader(GL_FRAGMENT_SHADER); @@ -889,11 +889,11 @@ rhi::Handle Gl2Rhi::create_program(const ProgramDesc& desc) { GLint max_length = 0; gl_->GetShaderiv(program.fragment_shader, GL_INFO_LOG_LENGTH, &max_length); - std::vector compile_error(max_length); + srb2::Vector compile_error(max_length); gl_->GetShaderInfoLog(program.fragment_shader, max_length, &max_length, compile_error.data()); gl_->DeleteShader(program.fragment_shader); - throw std::runtime_error(fmt::format("Fragment shader compilation failed: {}", std::string(compile_error.data()))); + throw std::runtime_error(fmt::format("Fragment shader compilation failed: {}", String(compile_error.data()))); } program.program = gl_->CreateProgram(); @@ -907,13 +907,13 @@ rhi::Handle Gl2Rhi::create_program(const ProgramDesc& desc) { GLint max_length = 0; gl_->GetProgramiv(program.program, GL_INFO_LOG_LENGTH, &max_length); - std::vector link_error(max_length); + srb2::Vector link_error(max_length); gl_->GetProgramInfoLog(program.program, max_length, &max_length, link_error.data()); gl_->DeleteProgram(program.program); gl_->DeleteShader(program.fragment_shader); gl_->DeleteShader(program.vertex_shader); - throw std::runtime_error(fmt::format("Pipeline program link failed: {}", std::string(link_error.data()))); + throw std::runtime_error(fmt::format("Pipeline program link failed: {}", String(link_error.data()))); } // get attribute information @@ -936,7 +936,7 @@ rhi::Handle Gl2Rhi::create_program(const ProgramDesc& desc) GL_ASSERT; GLint location = gl_->GetAttribLocation(program.program, name); GL_ASSERT; - program.attrib_locations[std::string(name)] = location; + program.attrib_locations[String(name)] = location; } // get uniform information @@ -959,7 +959,7 @@ rhi::Handle Gl2Rhi::create_program(const ProgramDesc& desc) GL_ASSERT; GLint location = gl_->GetUniformLocation(program.program, name); GL_ASSERT; - program.uniform_locations[std::string(name)] = location; + program.uniform_locations[String(name)] = location; } Handle program_handle = program_slab_.insert(std::move(program)); diff --git a/src/rhi/gl2/gl2_rhi.hpp b/src/rhi/gl2/gl2_rhi.hpp index c12af9445..f8b62987b 100644 --- a/src/rhi/gl2/gl2_rhi.hpp +++ b/src/rhi/gl2/gl2_rhi.hpp @@ -14,11 +14,11 @@ #include #include #include -#include #include -#include -#include +#include "../../core/hash_map.hpp" +#include "../../core/string.h" +#include "../../core/vector.hpp" #include "../rhi.hpp" namespace srb2::rhi @@ -71,7 +71,7 @@ struct Gl2Platform virtual ~Gl2Platform(); virtual void present() = 0; - virtual std::tuple, std::vector> find_shader_sources(const char* name) = 0; + virtual std::tuple, srb2::Vector> find_shader_sources(const char* name) = 0; virtual Rect get_default_framebuffer_dimensions() = 0; }; @@ -98,8 +98,8 @@ struct Gl2Program : public rhi::Program uint32_t vertex_shader = 0; uint32_t fragment_shader = 0; uint32_t program = 0; - std::unordered_map attrib_locations; - std::unordered_map uniform_locations; + srb2::HashMap attrib_locations; + srb2::HashMap uniform_locations; }; struct Gl2ActiveUniform @@ -121,14 +121,14 @@ class Gl2Rhi final : public Rhi Handle current_index_buffer_; - std::unordered_map framebuffers_ {16}; + srb2::HashMap framebuffers_ {16}; struct DefaultRenderPassState { bool clear = false; }; using RenderPassState = std::variant; - std::vector render_pass_stack_; + srb2::Vector render_pass_stack_; std::optional> current_program_; PrimitiveType current_primitive_type_ = PrimitiveType::kPoints; uint32_t index_buffer_offset_ = 0; diff --git a/src/rhi/shader_load_context.cpp b/src/rhi/shader_load_context.cpp index 790fd5df1..ad3ffcf81 100644 --- a/src/rhi/shader_load_context.cpp +++ b/src/rhi/shader_load_context.cpp @@ -12,6 +12,9 @@ #include +#include "../core/string.h" +#include "../core/vector.hpp" + using namespace srb2; using namespace rhi; @@ -19,27 +22,27 @@ ShaderLoadContext::ShaderLoadContext() = default; void ShaderLoadContext::set_version(std::string_view version) { - version_ = fmt::format("#version {}\n", version); + version_ = srb2::format("#version {}\n", version); } -void ShaderLoadContext::add_source(const std::string& source) +void ShaderLoadContext::add_source(const srb2::String& source) { sources_.push_back(source); } -void ShaderLoadContext::add_source(std::string&& source) +void ShaderLoadContext::add_source(srb2::String&& source) { sources_.push_back(std::move(source)); } void ShaderLoadContext::define(std::string_view name) { - defines_.append(fmt::format("#define {}\n", name)); + defines_.append(srb2::format("#define {}\n", name)); } -std::vector ShaderLoadContext::get_sources_array() +srb2::Vector ShaderLoadContext::get_sources_array() { - std::vector ret; + srb2::Vector ret; ret.push_back(version_.c_str()); ret.push_back(defines_.c_str()); diff --git a/src/rhi/shader_load_context.hpp b/src/rhi/shader_load_context.hpp index d11e38f6a..1bc458ea8 100644 --- a/src/rhi/shader_load_context.hpp +++ b/src/rhi/shader_load_context.hpp @@ -11,29 +11,30 @@ #ifndef __SRB2_RHI_SHADER_LOAD_CONTEXT_HPP__ #define __SRB2_RHI_SHADER_LOAD_CONTEXT_HPP__ -#include #include -#include + +#include "../core/string.h" +#include "../core/vector.hpp" namespace srb2::rhi { class ShaderLoadContext { - std::string version_; - std::string defines_; - std::vector sources_; + srb2::String version_; + srb2::String defines_; + srb2::Vector sources_; public: ShaderLoadContext(); void set_version(std::string_view version); - void add_source(const std::string& source); - void add_source(std::string&& source); + void add_source(const srb2::String& source); + void add_source(srb2::String&& source); void define(std::string_view name); - std::vector get_sources_array(); + srb2::Vector get_sources_array(); }; }; // namespace srb2::rhi diff --git a/src/sanitize.cpp b/src/sanitize.cpp index 6862e0ec8..597a27345 100644 --- a/src/sanitize.cpp +++ b/src/sanitize.cpp @@ -11,9 +11,9 @@ #include #include #include -#include #include +#include "core/string.h" #include "doomtype.h" #include "sanitize.h" #include "v_draw.hpp" @@ -34,7 +34,7 @@ bool color_filter(char c) } template -std::string& filter_out(std::string& out, const std::string_view& range, F filter) +srb2::String& filter_out(srb2::String& out, const std::string_view& range, F filter) { std::remove_copy_if( range.begin(), @@ -62,9 +62,9 @@ int hexconv(int c) namespace srb2::sanitize { -std::string sanitize(std::string_view in, SanitizeMode mode) +srb2::String sanitize(std::string_view in, SanitizeMode mode) { - std::string out; + srb2::String out; return filter_out(out, in, [mode] { switch (mode) @@ -78,9 +78,9 @@ std::string sanitize(std::string_view in, SanitizeMode mode) }()); } -std::string parse_carets(std::string_view in, ParseMode mode) +srb2::String parse_carets(std::string_view in, ParseMode mode) { - std::string out; + srb2::String out; using std::size_t; for (;;) diff --git a/src/sanitize.h b/src/sanitize.h index 3bb07c1b3..0fe04e44d 100644 --- a/src/sanitize.h +++ b/src/sanitize.h @@ -14,9 +14,10 @@ #include "doomtype.h" #ifdef __cplusplus -#include #include +#include "core/string.h" + namespace srb2::sanitize { @@ -33,10 +34,10 @@ enum class ParseMode }; // sanitizes string of all 0x80 codes -std::string sanitize(std::string_view in, SanitizeMode mode); +srb2::String sanitize(std::string_view in, SanitizeMode mode); // sanitizes string of all 0x80 codes then parses caret codes -std::string parse_carets(std::string_view in, ParseMode mode); +srb2::String parse_carets(std::string_view in, ParseMode mode); }; // namespace srb2 diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index fd6ff45c2..827d6314b 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -17,10 +17,10 @@ #include "../d_main.h" #include "../m_misc.h"/* path shit */ #include "../i_system.h" +#include "../core/string.h" #include #include -#include #include @@ -227,14 +227,14 @@ ChDirToExe (void) } #endif -static void walk_exception_stack(std::string& accum, bool nested) { +static void walk_exception_stack(srb2::String& accum, bool nested) { if (nested) accum.append("\n Caused by: Unknown exception"); else accum.append("Uncaught exception: Unknown exception"); } -static void walk_exception_stack(std::string& accum, const std::exception& ex, bool nested) { +static void walk_exception_stack(srb2::String& accum, const std::exception& ex, bool nested) { if (nested) accum.append("\n Caused by: "); else @@ -331,11 +331,11 @@ int main(int argc, char **argv) D_SRB2Loop(); } catch (const std::exception& ex) { - std::string exception; + srb2::String exception; walk_exception_stack(exception, ex, false); I_Error("%s", exception.c_str()); } catch (...) { - std::string exception; + srb2::String exception; walk_exception_stack(exception, false); I_Error("%s", exception.c_str()); } diff --git a/src/sdl/rhi_gl2_platform.cpp b/src/sdl/rhi_gl2_platform.cpp index 98a87f222..f3f10fe90 100644 --- a/src/sdl/rhi_gl2_platform.cpp +++ b/src/sdl/rhi_gl2_platform.cpp @@ -11,11 +11,13 @@ #include "rhi_gl2_platform.hpp" #include +#include #include #include -#include +#include "../core/string.h" +#include "../core/vector.hpp" #include "../cxxutil.hpp" #include "../doomstat.h" // mainwads #include "../w_wad.h" @@ -34,22 +36,22 @@ void SdlGl2Platform::present() SDL_GL_SwapWindow(window); } -static std::array glsllist_lump_names(const char* name) +static std::array glsllist_lump_names(const char* name) { - std::string vertex_list_name = fmt::format("rhi_glsllist_{}_vertex.txt", name); - std::string fragment_list_name = fmt::format("rhi_glsllist_{}_fragment.txt", name); + srb2::String vertex_list_name = fmt::format("rhi_glsllist_{}_vertex.txt", name); + srb2::String fragment_list_name = fmt::format("rhi_glsllist_{}_fragment.txt", name); return {std::move(vertex_list_name), std::move(fragment_list_name)}; } -static std::vector get_sources_from_glsllist_lump(const char* lumpname) +static srb2::Vector get_sources_from_glsllist_lump(const char* lumpname) { size_t buffer_size; if (!W_ReadShader(lumpname, &buffer_size, nullptr)) { throw std::runtime_error(fmt::format("Unable to find glsllist lump {}", lumpname)); } - std::string glsllist_lump_data; + srb2::String glsllist_lump_data; glsllist_lump_data.resize(buffer_size); if (!W_ReadShader(lumpname, &buffer_size, glsllist_lump_data.data())) { @@ -57,7 +59,7 @@ static std::vector get_sources_from_glsllist_lump(const char* lumpn } std::istringstream glsllist(glsllist_lump_data); - std::vector sources; + srb2::Vector sources; for (std::string line; std::getline(glsllist, line); ) { if (line.empty()) @@ -87,7 +89,7 @@ static std::vector get_sources_from_glsllist_lump(const char* lumpn { throw std::runtime_error(fmt::format("Unable to find glsl source lump lump {}", line)); } - std::string source_lump; + srb2::String source_lump; source_lump.resize(source_lump_size); if (!W_ReadShader(line.c_str(), &source_lump_size, source_lump.data())) { @@ -100,13 +102,13 @@ static std::vector get_sources_from_glsllist_lump(const char* lumpn return sources; } -std::tuple, std::vector> +std::tuple, srb2::Vector> SdlGl2Platform::find_shader_sources(const char* name) { - std::array glsllist_names = glsllist_lump_names(name); + std::array glsllist_names = glsllist_lump_names(name); - std::vector vertex_sources = get_sources_from_glsllist_lump(glsllist_names[0].c_str()); - std::vector fragment_sources = get_sources_from_glsllist_lump(glsllist_names[1].c_str()); + srb2::Vector vertex_sources = get_sources_from_glsllist_lump(glsllist_names[0].c_str()); + srb2::Vector fragment_sources = get_sources_from_glsllist_lump(glsllist_names[1].c_str()); return std::make_tuple(std::move(vertex_sources), std::move(fragment_sources)); } diff --git a/src/sdl/rhi_gl2_platform.hpp b/src/sdl/rhi_gl2_platform.hpp index ea892fce5..acaf48618 100644 --- a/src/sdl/rhi_gl2_platform.hpp +++ b/src/sdl/rhi_gl2_platform.hpp @@ -11,6 +11,10 @@ #ifndef __SRB2_SDL_RHI_GL2_PLATFORM_HPP__ #define __SRB2_SDL_RHI_GL2_PLATFORM_HPP__ +#include + +#include "../core/string.h" +#include "../core/vector.hpp" #include "../rhi/gl2/gl2_rhi.hpp" #include "../rhi/rhi.hpp" @@ -26,7 +30,7 @@ struct SdlGl2Platform final : public Gl2Platform virtual ~SdlGl2Platform(); virtual void present() override; - virtual std::tuple, std::vector> + virtual std::tuple, Vector> find_shader_sources(const char* name) override; virtual Rect get_default_framebuffer_dimensions() override; }; diff --git a/src/v_draw.cpp b/src/v_draw.cpp index bb60b73c2..f8fe7df9c 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -8,8 +8,7 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include - +#include "core/hash_map.hpp" #include "doomdef.h" // skincolornum_t #include "doomtype.h" #include "hu_stuff.h" @@ -34,7 +33,7 @@ int Draw::TextElement::width() const Draw::TextElement& Draw::TextElement::parse(std::string_view raw) { - static const std::unordered_map translation = { + static const srb2::HashMap translation = { #define BUTTON(str, lower_bits) \ {str, 0xB0 | lower_bits},\ {str "_animated", 0xA0 | lower_bits},\ @@ -88,7 +87,7 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw) }; // When we encounter a Saturn button, what gamecontrol does it represent? - static const std::unordered_map inputdefinition = { + static const srb2::HashMap inputdefinition = { {sb_up, gc_up}, {sb_down, gc_down}, {sb_right, gc_right}, @@ -114,7 +113,7 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw) // What physical binds should appear as Saturn icons anyway? // (We don't have generic binds for stick/dpad directions, so // using the existing arrow graphics is the best thing here.) - static const std::unordered_map prettyinputs = { + static const srb2::HashMap prettyinputs = { {KEY_UPARROW, sb_up}, {KEY_DOWNARROW, sb_down}, {KEY_LEFTARROW, sb_left}, @@ -164,7 +163,7 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw) // SPECIAL: Generic button that we invoke explicitly, not via gamecontrol reference. // If we ever add anything else to this category, I promise I will create a real abstraction, // but for now, just hardcode the character replacements and pray for forgiveness. - + string_.push_back(0xEF); // Control code: "switch to descriptive input mode" string_.push_back(0xEB); // Control code: "large button" if (code == "dpad") @@ -198,7 +197,7 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw) // EXTRA: descriptiveinput values above 1 translate binds back to Saturn buttons, // with various modes for various fucked up 6bt pads - std::unordered_map padconfig = {}; + srb2::HashMap padconfig = {}; switch (cv_descriptiveinput[localplayer].value) { case 1: @@ -277,7 +276,7 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw) pad->second = pad->second & (0x0F); // original invocation has the animation bits, but the glyph bits come from the table - string_.push_back((it->second & 0xF0) | pad->second); + string_.push_back((it->second & 0xF0) | pad->second); } else { @@ -349,6 +348,11 @@ void Chain::fill(UINT8 color) const void Chain::string(const char* str, INT32 flags, Font font) const { + if (!str) + { + return; + } + const auto _ = Clipper(*this); flags |= default_font_flags(font); @@ -576,5 +580,10 @@ INT32 Draw::default_font_flags(Font font) fixed_t Draw::font_width(Font font, INT32 flags, const char* string) { + if (!string) + { + return 0; + } + return V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, flags, font_to_fontno(font), string); } diff --git a/src/v_draw.hpp b/src/v_draw.hpp index a5913a2b5..9fe9fa2c1 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -11,14 +11,14 @@ #ifndef __V_DRAW_HPP__ #define __V_DRAW_HPP__ -#include #include #include #include -#include #include +#include "core/hash_map.hpp" +#include "core/string.h" #include "doomdef.h" // skincolornum_t #include "doomtype.h" #include "screen.h" // BASEVIDWIDTH @@ -66,7 +66,7 @@ typedef enum } saturn_buttons_e; // Garden-variety standard gamepad -static const std::unordered_map standardpad = { +static const srb2::HashMap standardpad = { {nc_a, gb_a}, {nc_b, gb_b}, {nc_x, gb_x}, @@ -83,7 +83,7 @@ static const std::unordered_map standardpad = { // Standard gamepad, but evil Nintendo layout flip was applied by your // controller firmware or Steam Input—swap B/A and X/Y -static const std::unordered_map flippedpad = { +static const srb2::HashMap flippedpad = { {nc_a, gb_b}, {nc_b, gb_a}, {nc_x, gb_y}, @@ -99,7 +99,7 @@ static const std::unordered_map flippedpad = { }; // Saturn Type A - Retrobit Wired Dinput, RB RT LB LT (CZLR) -static const std::unordered_map saturntypeA = { +static const srb2::HashMap saturntypeA = { {nc_a, sb_a}, {nc_b, sb_b}, {nc_x, sb_x}, @@ -115,7 +115,7 @@ static const std::unordered_map saturntypeA = { }; // Saturn Type B - Retrobit Wireless Dinput, LB RB LT RT (CZLR) -static const std::unordered_map saturntypeB = { +static const srb2::HashMap saturntypeB = { {nc_a, sb_a}, {nc_b, sb_b}, {nc_x, sb_x}, @@ -131,7 +131,7 @@ static const std::unordered_map saturntypeB = { }; // Saturn Type C - Retrobit Xinput, RT LT LB RB (CZLR) -static const std::unordered_map saturntypeC = { +static const srb2::HashMap saturntypeC = { {nc_a, sb_a}, {nc_b, sb_b}, {nc_x, sb_x}, @@ -150,7 +150,7 @@ static const std::unordered_map saturntypeC = { // This cannot be disambiguated (shares L/R with type 1) // but is more spatially correct w/r/t SDL expectations // and standard arcade mapping (Z on top, C on bottom) -static const std::unordered_map saturntypeD = { +static const srb2::HashMap saturntypeD = { {nc_a, sb_a}, {nc_b, sb_b}, {nc_x, sb_x}, @@ -169,7 +169,7 @@ static const std::unordered_map saturntypeD = { // The Hori layout is, to my knowledge, the only 6bt one that has fully // unique buttons in every slot while having both bumpers and triggers, // so there's no way to accurately portray it without using generics. -static const std::unordered_map saturntypeE = { +static const srb2::HashMap saturntypeE = { {nc_a, sb_a}, {nc_b, sb_b}, {nc_x, sb_x}, @@ -263,7 +263,7 @@ public: public: explicit TextElement() {} - explicit TextElement(std::string string) : string_(string) {} + explicit TextElement(const srb2::String& string) : string_(string) {} template explicit TextElement(fmt::format_string format, Args&&... args) : @@ -271,14 +271,14 @@ public: { } - const std::string& string() const { return string_; } + const srb2::String& string() const { return string_; } std::optional font() const { return font_; } std::optional flags() const { return flags_; } std::optional as() const { return as_; } int width() const; - TextElement& string(std::string string) + TextElement& string(srb2::String string) { string_ = string; return *this; @@ -310,7 +310,7 @@ public: return *this; } private: - std::string string_; + srb2::String string_; std::optional font_; std::optional flags_; std::optional as_; @@ -357,7 +357,7 @@ public: Chain& colorize(UINT16 color); void text(const char* str) const { string(str, flags_, font_); } - void text(const std::string& str) const { text(str.c_str()); } + void text(const srb2::String& str) const { text(str.c_str()); } void text(const TextElement& elm) const { string(elm.string().c_str(), elm.flags().value_or(flags_), elm.font().value_or(font_)); @@ -370,7 +370,7 @@ public: void patch(patch_t* patch) const; void patch(const char* name) const { patch(Draw::cache_patch(name)); } - void patch(const std::string& name) const { patch(name.c_str()); } + void patch(const srb2::String& name) const { patch(name.c_str()); } void thumbnail(UINT16 mapnum) const; diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 8f2117051..ac91947fe 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -322,9 +322,9 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) { srb2::StandingJson standing {}; standing.ranking = data.pos[data.numplayers]; - standing.name = std::string(player_names[i]); + standing.name = srb2::String(player_names[i]); standing.demoskin = players[i].skin; - standing.skincolor = std::string(skincolors[players[i].skincolor].name); + standing.skincolor = srb2::String(skincolors[players[i].skincolor].name); standing.timeorscore = data.val[data.numplayers]; standings.standings.emplace_back(std::move(standing)); }