Compare commits

...

60 Commits

Author SHA1 Message Date
cat
67df059f59 Append data at the end of the shared binary 2026-03-23 03:54:34 +02:00
cat
82f56f70a9 Added FunctionTraits 2026-03-16 09:31:09 +02:00
cat
b8c21ebbde Removed LogLevel being enum class 2026-03-16 09:30:29 +02:00
cat
9a684ae93d Added a pointless throw to stop GCC complaining 2026-03-09 20:30:27 +02:00
cat
2256c633d3 Switched to Corrade's arrays on MultiQueryResult@DualKey Map 2026-03-09 18:23:52 +02:00
cat
7c92cde143 Tweaked LogFormatted to squash fake clangd errors 2026-03-08 01:00:18 +02:00
cat
88d4695596 Switched to Corrade's string over STL 2026-03-07 14:44:29 +02:00
cat
55f12fcd69 Fixing quoting on xohiro 2026-03-07 14:43:59 +02:00
cat
27f50ffb35 Decided to dynamically link and ship corrade with tourmaline 2026-03-07 14:43:20 +02:00
cat
ab6841ab85 Testing Corrade on Logging 2026-03-06 16:35:21 +02:00
cat
a06140187d Made the paths absolute, added CMAKE_EXPORT_COMPILER_COMMANDS to CMakeLists.txt 2026-03-06 16:34:39 +02:00
cat
ce9ea6752d Added Corrade as a dependency and also Fetchscript 2026-03-06 16:33:30 +02:00
cat
58c69e4aa5 Added how to compile to README 2026-03-06 16:32:43 +02:00
cat
beab60fcd2 Made includes relative addresses 2026-03-05 02:12:55 +02:00
aafd2c05d2 Update README.md 2026-03-04 06:22:15 +01:00
b18e1b9970 Update README.md 2026-03-04 06:21:27 +01:00
cat
a19776b930 Added Container Options for fine tuning, fine tuned hashmap for DKM 2026-03-03 00:22:22 +02:00
cat
30cbc7cd1c Fixed DualKeyMap MultiQuery not recording how many found properly 2026-03-02 18:20:11 +02:00
cat
cecea2eaff Upped the load factor to reduce allocations 2026-03-02 17:28:14 +02:00
cat
e185c77cb8 Fixed speeeling miskate :) 2026-03-02 17:27:06 +02:00
cat
084eb8266b Changed hashmaps to not use pointers, saving on allocations 2026-03-02 16:57:52 +02:00
cat
c388f20429 Started adding hashmap instead of std::vector as QueryWithMany return 2026-03-02 09:13:04 +02:00
cat
5112f2a3a3 Bugfix: Hash position was calculated before rehash 2026-03-02 08:59:42 +02:00
cat
7fd71ab0ac Added rehashing for growing and shrinking 2026-03-02 07:56:34 +02:00
cat
73ddc307eb Added noexcepts and nodiscards 2026-03-02 04:08:25 +02:00
cat
ffe6dd83ca Hashmap added Count() 2026-03-02 04:02:46 +02:00
cat
07c9ed49c7 Hashmap added checking for insertion and fixed remove 2026-03-02 03:55:23 +02:00
cat
2326ddfee7 Fix guard's position 2026-03-01 05:07:50 +02:00
cat
fdb84b30b7 Added a Hashmap (WIP) 2026-03-01 05:07:12 +02:00
cat
af8b68ca46 Added checks for tombstones 2026-02-27 09:16:29 +02:00
cat
0014ce0602 First RC of QueryWithAll, needs its erase logic improved 2026-02-27 09:06:16 +02:00
cat
09b58fe1f5 addded .cache to gitignore 2026-02-27 09:06:04 +02:00
cat
4eb846f215 const std::string& to std::string_view and static output buffer on Log 2026-02-26 20:34:23 +02:00
cat
3d17902844 Tweaking DualKey Map to new Concept.hpp, and doing better templating 2026-02-26 19:24:06 +02:00
cat
3910827805 Moved concepts out of containers and several tweaks on concepts.hpp 2026-02-26 01:32:16 +02:00
cat
b67b042be3 Reorganised DualkeyMap.hpp and added MultiKeyResult 2026-02-23 06:31:32 +02:00
cat
31b3f2033d Added Either concept also moved Hashing.hpp to Concept.hpp 2026-02-23 06:30:37 +02:00
cat
3c2744854b Second iter of MultiQuery, splitted the logic to queryWithMany 2026-02-15 17:55:33 +02:00
cat
f20e75a9ee Added more proper fails and QueryWithAll first iteration (not fully impl) 2026-02-15 04:15:59 +02:00
cat
bd3be51abd Errors throw, while criticals terminate. 2026-02-03 13:19:59 +02:00
cat
f2d429109b Changing casing of dualkey map to PascalCase 2026-02-01 00:54:25 +02:00
2f84b47fec Update README.md 2026-01-31 14:50:29 +02:00
ec58bb7990 Showing appreciation
you can cry now (lol)
2026-01-31 13:30:08 +01:00
cat
0ac8d26578 Allow moving for DualKeyMap 2026-01-31 14:14:57 +02:00
cat
7cd43a2215 World should be movable only 2026-01-31 14:11:48 +02:00
cat
edb0055224 UUID has been updated to use a better hash and no unnecessary allocations (Big thanks to mosra!) 2026-01-31 13:45:15 +02:00
cat
ff062567d8 Renamed BaseComponent to ECS::Component, removed enabled, renamed Position to Base 2026-01-30 18:46:49 +02:00
cat
16c1a2c620 Added a scan with hash function and labeled the arguments 2026-01-30 16:42:28 +02:00
cat
baa8cc351d Simplified DualKeyHash 2026-01-30 16:27:03 +02:00
cat
3fc4f3ec84 Added basics of the ECS world's life cycle 2026-01-30 15:58:53 +02:00
cat
cd59ed656a Enabled for entities is now a friend of ECS::World 2026-01-30 15:58:13 +02:00
cat
d21bc60024 Making use of std::stack instead of std::queue 2026-01-30 15:56:51 +02:00
cat
af05dfcf0d Fixed the wrong naming of Components.cpp 2026-01-30 15:56:33 +02:00
cat
dadbfbc085 Continuing to add Enabled 2026-01-30 13:58:24 +02:00
cat
610a22a852 Moved base component from ECS.hpp and added Enabled 2026-01-30 10:12:14 +02:00
cat
f760cfd658 Added EntityExists and HasComponent 2026-01-28 14:29:47 +02:00
cat
4cc10ddc21 Added Scan() to go through the dualkey map 2026-01-28 14:22:04 +02:00
cat
7000aa712b Removing needless entity pointer storing in BaseComponent 2026-01-28 14:02:28 +02:00
cat
12796fa31b CreateEntity should be nodiscard 2026-01-28 13:47:28 +02:00
cat
c1637a2a2e Hashes on dualkey map need to be also const 2026-01-28 13:47:07 +02:00
18 changed files with 764 additions and 243 deletions

3
.gitignore vendored
View File

@@ -165,3 +165,6 @@ build-iPhoneSimulator/
# Custom
# For personal testing cases
test/
# clangd
.cache/

View File

@@ -6,33 +6,98 @@
# obtain one at http://mozilla.org/MPL/2.0/.
cmake_minimum_required(VERSION 3.10)
cmake_policy(SET CMP0135 NEW)
project(Tourmaline VERSION 1)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address")
endif()
include(GNUInstallDirs)
include(FetchContent)
# Original - https://github.com/novelrt/NovelRT/blob/c877c1e870d62df98935489e9682d93b009fb2fd/ThirdParty/CMakeLists.txt#L6
# Modified version by williamjcm
macro(external_dependency name)
FetchContent_Declare(${name}
${ARGN}
EXCLUDE_FROM_ALL
PREFIX "${CMAKE_CURRENT_BINARY_DIR}/${name}"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/tmp"
STAMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/stamp"
DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/dl"
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/src"
SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/build"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/bin"
INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/inst"
LOG_DIR "${CMAKE_CURRENT_BINARY_DIR}/${name}/log"
)
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}/cmake/${name}")
endmacro()
# Third Party Libraries
external_dependency(Corrade
URL https://github.com/mosra/corrade/archive/2b7251d8bd8833a12f0d9b8deffca7a290340d3c.zip
URL_HASH SHA256=77ed07d373792ce05a64b87c84e7d4687965d6040df4e17b6e9922ca1cbd88c8
)
foreach(dep
Corrade
)
message(STATUS "Fetching ${dep}...")
add_subdirectory(external/${dep})
endforeach()
# Building
add_library(${PROJECT_NAME} SHARED
"source/Systems/ECS/Component.cpp"
"source/Systems/ECS/Components.cpp"
"source/Systems/ECS/World.cpp"
"source/Systems/Logging.cpp"
"source/Systems/Random.cpp"
"source/Types/UUID.cpp")
"source/Types/UUID.cpp"
)
# Actual linking
target_link_libraries(${PROJECT_NAME} PUBLIC
Corrade::Main
Corrade::Containers
Corrade::Utility
Corrade::PluginManager
)
# Module stuff
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
# Nothing to link right now
target_link_libraries(${PROJECT_NAME})
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/headers>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/>
$<BUILD_INTERFACE:${corrade_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}External>
)
FetchContent_GetProperties(Corrade SOURCE_DIR corrade_SOURCE_DIR)
FetchContent_GetProperties(Corrade BINARY_DIR corrade_BINARY_DIR)
install(
DIRECTORY
"${corrade_SOURCE_DIR}/src/Corrade/"
"${corrade_BINARY_DIR}/src/Corrade/"
DESTINATION "include/${PROJECT_NAME}External/Corrade"
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
)
# A way to live forever
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND printf "Dedicated to my beloved Goma, I love you. - Dora" >> $<TARGET_FILE:${PROJECT_NAME}>
)
install(
@@ -40,7 +105,14 @@ install(
EXPORT ${PROJECT_NAME}Targets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include
INCLUDES DESTINATION include/${PROJECT_NAME}
)
install(
TARGETS CorradeMain CorradeUtility CorradeContainers CorradePluginManager
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include/${PROJECT_NAME}External
)
install(DIRECTORY headers/ DESTINATION include/${PROJECT_NAME})

View File

@@ -1,15 +1,42 @@
# Tourmaline Engine
Tormaline Engine is a game engine created for C++23. [Source Code](https://git.thenight.club/cat/Tourmaline-Engine/).
Tormaline Engine is a game engine created for game development with C++23.
### Currently the project is still trying to incorporate following parts (in no particular order):
- [ ] ECS (Builtin)
- [X] Logging (Builtin)
- [X] Random Generation (Builtin)
- [ ] Graphics (Magnum Graphics)
- [ ] Physics Integration (ReactPhysics3D)
- [ ] Audio (Builtin + miniaudio)
- [ ] Particles (Builtin)
- [ ] Video playback (Builtin + FFmpeg(subject to change))
- [ ] Asset Archiving (Searching for a library)
- [ ] UI (Magnum UI)
- [ ] Shaders (Builtin)
A lot of the info regarding Tourmaline has been removed from here. This is due to project still being in early phases of development.
# Usability Status
Tourmaline is by no means currently usable. The project is incredible volatile with constant changes and improvements. Please wait until a release is made.
# 3rd Party Used Libraries Credits
However if you cannot just help yourself you can compile a tourmaline demo by running
```
g++ program.cpp -std=c++23 -lTourmaline -lCorradeUtility -lCorradePluginManager -I/usr/local/include/TourmalineExternal -o program
```
# 3rd Party Libraries Credits
- [Corrade/Magnum](https://magnum.graphics/) - graphics middleware by Vladimír "Mosra" Vondruš.
- [miniaudio](https://miniaud.io/) - audio playback and capture library by David "Mackron" Reid.
- [Xohiro](https://github.com/david-cortes/xoshiro_cpp/blob/master/xoshiro.h) implementation by David Blackman and Sebastiano Vigna
- [Xohiro](https://github.com/david-cortes/xoshiro_cpp/blob/master/xoshiro.h) implementation by David Blackman and Sebastiano Vigna.
# What is Tourmaline and Why is that the name?
# Special Thanks
- [Lars "harmonyisdead"](https://github.com/larsl2005) for hosting the thenight.club services.
- [Vladimír "Mosra" Vondruš](https://github.com/mosra) for their mentorship of my C++ knowledge/projects, and the people at the [magnum gitter channel](https://matrix.to/#/#mosra_magnum:gitter.im), for their continous support and feedback of this project.
- [Kae "voxelfoxkae"](https://voxelfox.co.uk/) for mentorship.
# Not-so frequently asked questions
### Version Scheming
Tourmaline uses a single digit that ticks up by 1 for each version. There are no major or minor updates or versions. The project can be seen as a rolling release project.
### What is Tourmaline and Why is that the name?
From [Wikipedia](https://en.wikipedia.org/wiki/Tourmaline)
> Tourmaline is a crystalline silicate mineral group in which boron is compounded with elements such as aluminium, iron, magnesium, sodium, lithium, or potassium.
@@ -17,14 +44,3 @@ From [Find Gemstone](https://www.findgemstone.com/blog/what-is-tourmaline-used-f
> Tourmaline has unique electric properties, including the ability to generate an electric charge as well as maintain electromagnetic radiation. These characteristics make tourmaline useful in the production of a range of electronics.
In short Tourmaline is a very versatile "Gem". This name is given to the third iteration of a project that once only concern itself with Video playback on Magnum graphics.
## Short History
Initially this project was called **MagnumVideo**, as goal was to allow video playback on [Magnum Graphics](https://magnum.graphics/). Due to Magnum Graphic's OpenAL wrapper being very poorly, made the project required me to include an audio engine with the video playback.
> Original MagnumVideo was able to implement basic video playback with static frame time
So the project grew into **Overcharged Toolset** (MagnumVideo -> ChargeVideo), with goal to extend Magnum with Seamless Video, Audio, ECS, Shader, Particle, etc... systems. Each package aimed to be independent out of the box and only require each other if desired. However this was incredibly unsustainable and made development enjoyability and API quality very poor.
> ChargeVideo improved upon MagnumVideo with more supported types and PTS based timing. ChargeAudio was added to allow audio playback with video synced.
Around the time that Overcharged has been having an identity crisis, a dear friend in a libera IRC chat has introduced me to the wonderful world of [Ruby](https://www.ruby-lang.org/en/). I spent few days trying to combine two of my favourite things ever (Ruby's ease, and C++'s speed & control), this has led me to finally ditch Overcharged and start with a clean slate.
Therefore the name is basically "Overcharged + Ruby (a gem) = Tourmaline (Gem used to make electronics)" )

16
external/Corrade/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,16 @@
include(FetchContent)
# Building options
set(CORRADE_BUILD_STATIC OFF)
# Feature options
set(CORRADE_WITH_MAIN ON)
set(CORRADE_WITH_UTILITY ON)
set(CORRADE_WITH_PLUGINMANAGER ON)
set(CORRADE_WITH_INTERCONNECT OFF)
set(CORRADE_WITH_TESTSUITE OFF)
if(NOT CMAKE_CROSSCOMPILING)
set(CORRADE_WITH_RC ON)
endif()
FetchContent_MakeAvailable(Corrade)

60
headers/Concepts.hpp Normal file
View File

@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: Dora "cat" <cat@thenight.club>
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef GUARD_TOURMALINE_CONCEPTS_H
#define GUARD_TOURMALINE_CONCEPTS_H
#include <concepts>
#include <functional>
#include <tuple>
#include <type_traits>
namespace Tourmaline::Concepts {
template <typename T>
concept Hashable = std::equality_comparable<T> && requires(T x) {
{ std::hash<T>{}(x) } -> std::convertible_to<std::size_t>;
};
template <typename Base, typename Type1, typename Type2>
concept Either = std::same_as<Base, Type1> || std::same_as<Base, Type2>;
// Oh C++ and your jank
template <typename Base, typename Type1, typename Type2> struct _opposite_of {
using type = std::conditional_t<std::is_same_v<Base, Type1>, Type2, Type1>;
};
template <typename Base, typename Type1, typename Type2>
requires Either<Base, Type1, Type2>
using OppositeOf = _opposite_of<Base, Type1, Type2>::type;
// heavily inspired by
// https://github.com/aminroosta/sqlite_modern_cpp/blob/master/hdr/sqlite_modern_cpp/utility/function_traits.h
template <typename> struct FunctionTraits;
template <typename Function>
struct FunctionTraits
: public FunctionTraits<
decltype(&std::remove_reference_t<Function>::operator())> {};
template <typename Return, typename Class, typename... Arguments>
struct FunctionTraits<Return (Class::*)(Arguments...) const>
: FunctionTraits<Return (*)(Arguments...)> {};
template <typename Return, typename Class, typename... Arguments>
struct FunctionTraits<Return (Class::*)(Arguments...)>
: FunctionTraits<Return (*)(Arguments...)> {};
template <typename Return, typename... Arguments>
struct FunctionTraits<Return (*)(Arguments...)> {
using returnType = Return;
using arguments = std::tuple<Arguments...>;
template <std::size_t index>
using argument = std::tuple_element_t<index, arguments>;
static constexpr std::size_t argumentCount = sizeof...(Arguments);
};
} // namespace Tourmaline::Concepts
#endif

View File

@@ -6,16 +6,23 @@
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef GUARD_TOURMALINE_CONTAINEROPTIONS_H
#define GUARD_TOURMALINE_CONTAINEROPTIONS_H
#ifndef GUARD_TOURMALINE_HASHING_H
#define GUARD_TOURMALINE_HASHING_H
#include <concepts>
#include <functional>
#include <cstddef>
#include <cstdint>
namespace Tourmaline::Containers {
template <typename T>
concept Hashable = std::equality_comparable<T> && requires(T x) {
{ std::hash<T>{}(x) } -> std::convertible_to<std::size_t>;
struct HashmapOptions {
float loadFactor = 0.75f;
float minimizeFactor = 0.20f;
float leaningFactor = 2.5f;
std::size_t minimumBucketCount = 256;
std::size_t reservedBucketSpace = 4;
};
struct DualKeyMapOptions {
std::uint64_t baseReservation = 2048;
};
} // namespace Tourmaline::Containers
#endif

View File

@@ -8,32 +8,50 @@
*/
#ifndef GUARD_TOURMALINE_DUALKEYMAP_H
#define GUARD_TOURMALINE_DUALKEYMAP_H
#include "../Concepts.hpp"
#include "../Systems/Logging.hpp"
#include "Hashing.hpp"
#include "ContainerOptions.hpp"
#include "Hashmap.hpp"
#include "Corrade/Containers/Array.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <queue>
#include <stack>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
namespace Tourmaline::Containers {
template <Hashable AKey, Hashable BKey, typename Value,
uint64_t baseReservation = 2048, double maxTombstoneRatio = 0.25>
template <Concepts::Hashable AKey, Concepts::Hashable BKey, typename Value,
DualKeyMapOptions Options = {}>
class DualkeyMap {
public:
// Return Types
template <typename OppositeKey>
requires Concepts::Either<OppositeKey, AKey, BKey>
struct MultiQueryResult {
// Having to use pointers here over references was not fun
// but it was for greater good
const OppositeKey *oppositeKey;
Corrade::Containers::Array<Value *> valueQueryResults;
std::size_t howManyFound = 1;
};
using QueryResult =
std::pair<std::variant<std::monostate, std::reference_wrapper<const AKey>,
std::reference_wrapper<const BKey>>,
Value &>;
using Entry = std::tuple<const AKey &, const BKey &, Value &>;
DualkeyMap() { hashList.reserve(baseReservation); }
// Construct/Destruct
DualkeyMap() { hashList.reserve(Options.baseReservation); }
~DualkeyMap() {
// I'm sure there is a better way to do this
for (DualkeyHash *hash : hashList) {
@@ -43,34 +61,36 @@ public:
}
}
Entry insert(AKey firstKey, BKey secondKey, Value value) {
std::size_t firstKeyHash = std::hash<AKey>{}(firstKey);
std::size_t secondKeyHash = std::hash<BKey>{}(secondKey);
DualkeyHash *hash =
new DualkeyHash(firstKeyHash, std::move(firstKey), secondKeyHash,
// No copying due to the container expected to be the sole
// owner of the data
DualkeyMap(const DualkeyMap &) = delete;
DualkeyMap &operator=(const DualkeyMap &) = delete;
// Public controls
Entry Insert(AKey firstKey, BKey secondKey, Value value) {
DualkeyHash *hash = new DualkeyHash(std::move(firstKey),
std::move(secondKey), std::move(value));
if (graveyard.empty()) {
hashList.push_back(hash);
} else {
std::size_t tombstone = graveyard.front();
hashList[graveyard.top()] = hash;
graveyard.pop();
hashList[tombstone] = hash;
}
return {hash->firstKey, hash->secondKey, hash->value};
}
std::size_t remove(std::optional<AKey> firstKey,
std::size_t Remove(std::optional<AKey> firstKey,
std::optional<BKey> secondKey) {
bool isFirstKeyGiven = firstKey.has_value();
bool isSecondKeyGiven = secondKey.has_value();
if (!isFirstKeyGiven && !isSecondKeyGiven) [[unlikely]] {
Systems::Logging::Log("Failed to Delete! Dualkey maps require at least 1 "
"key to be given, doing nothing.",
"Dualkey Map", Systems::Logging::LogLevel::Warning);
return 0;
Systems::Logging::Log(
"Failed to Delete! DualkeyMap::Delete require at least 1 "
"key to be given! Terminating",
"Dualkey Map", Systems::Logging::LogLevel::Critical);
}
std::size_t firstKeyHash =
@@ -80,7 +100,6 @@ public:
std::size_t index = 0, amountDeleted = 0;
uint8_t stateOfIndexing = isFirstKeyGiven + (isSecondKeyGiven << 1);
for (DualkeyHash *hash : hashList) {
// Tombstone
if (hash == nullptr) [[unlikely]] {
continue;
@@ -107,7 +126,7 @@ public:
}
break;
case 3:
case 3: // Both given
if (firstKeyHash == hash->firstKeyHash &&
secondKeyHash == hash->secondKeyHash &&
firstKey.value() == hash->firstKey &&
@@ -124,17 +143,23 @@ public:
return amountDeleted;
}
[[nodiscard("Discarding an expensive operation's result!")]]
std::vector<QueryResult> query(std::optional<AKey> firstKey,
[[nodiscard]]
std::size_t Count() {
return hashList.size() - graveyard.size();
}
// Queries
[[nodiscard("Discarding an expensive query!")]]
std::vector<QueryResult> Query(std::optional<AKey> firstKey,
std::optional<BKey> secondKey) {
bool isFirstKeyGiven = firstKey.has_value();
bool isSecondKeyGiven = secondKey.has_value();
if (!isFirstKeyGiven && !isSecondKeyGiven) [[unlikely]] {
Systems::Logging::Log("Failed to Query! Dualkey maps require at least 1 "
"key to be given, returning an empty vector.",
"Dualkey Map", Systems::Logging::LogLevel::Warning);
return {};
Systems::Logging::Log(
"Failed to Query! DualkeyMap::Query require at least 1 "
"key to be given! Terminating",
"Dualkey Map", Systems::Logging::LogLevel::Critical);
}
std::size_t firstKeyHash =
isFirstKeyGiven ? std::hash<AKey>{}(firstKey.value()) : 0;
@@ -180,36 +205,146 @@ public:
return finishedQuery;
}
[[nodiscard]]
std::size_t count() {
return hashList.size() - graveyard.size();
template <typename Key,
typename OppositeKey = Concepts::OppositeOf<Key, AKey, BKey>>
requires Concepts::Either<Key, AKey, BKey>
[[nodiscard("Discarding a very expensive query!")]]
std::vector<MultiQueryResult<OppositeKey>>
QueryWithAll(const Corrade::Containers::Array<Key> &keys) {
std::vector<MultiQueryResult<OppositeKey>> queryResult =
queryWithMany<Key>(keys);
std::erase_if(queryResult,
[keyCount = keys.size()](
const MultiQueryResult<OppositeKey> &queryRecord) {
return queryRecord.howManyFound != keyCount;
});
return queryResult;
}
// No copying, No moving. Moving may be valid in the future.
// However as of now it is not a wise way to use this map.
DualkeyMap(const DualkeyMap &) = delete;
DualkeyMap(DualkeyMap &&) = delete;
DualkeyMap &operator=(const DualkeyMap &) = delete;
DualkeyMap &operator=(DualkeyMap &&) = delete;
void Scan(std::function<bool(const std::size_t firstKeyHash,
const std::size_t secondKeyHash, Value &value)>
scanFunction) {
for (DualkeyHash *hash : hashList) {
if (hash == nullptr) {
continue;
}
if (scanFunction(hash->firstKeyHash, hash->secondKeyHash, hash->value)) {
return;
}
}
}
void Scan(std::function<bool(const AKey &firstKey, const BKey &secondKey,
Value &value)>
scanFunction) {
for (DualkeyHash *hash : hashList) {
if (hash == nullptr) {
continue;
}
if (scanFunction(hash->firstKey, hash->secondKey, hash->value)) {
return;
}
}
}
private:
// Interal data structures
struct DualkeyHash {
DualkeyHash(std::size_t firstKeyHash, AKey &&firstKey,
std::size_t secondKeyHash, BKey &&secondKey, Value &&value)
: firstKeyHash(firstKeyHash), secondKeyHash(secondKeyHash),
firstKey(std::move(firstKey)), secondKey(std::move(secondKey)),
DualkeyHash(AKey &&firstKey, BKey &&secondKey, Value &&value)
: firstKey(std::move(firstKey)), secondKey(std::move(secondKey)),
firstKeyHash(std::hash<AKey>{}(this->firstKey)),
secondKeyHash(std::hash<BKey>{}(this->secondKey)),
value(std::move(value)) {}
std::size_t firstKeyHash = 0;
std::size_t secondKeyHash = 0;
const AKey firstKey;
const BKey secondKey;
const std::size_t firstKeyHash;
const std::size_t secondKeyHash;
mutable Value value;
};
// It makes more sense to store the individual hash
// Actual data
std::vector<DualkeyHash *> hashList;
std::queue<std::size_t> graveyard;
std::stack<std::size_t> graveyard;
// Interal querying
template <typename Key,
typename OppositeKey = Concepts::OppositeOf<Key, AKey, BKey>>
inline std::vector<MultiQueryResult<OppositeKey>>
queryWithMany(const Corrade::Containers::Array<Key> &keys) {
constexpr bool searchingInFirstKey = std::is_same_v<Key, AKey>;
std::size_t keyCount = keys.size();
// I really can't wait for C++26 contracts
if (keyCount == 0) {
Systems::Logging::Log("Failed to Query! QueryWithAll require at least 2 "
"key to be given, zero was given! Terminating",
"Dualkey Map",
Systems::Logging::LogLevel::Critical);
}
// Hoping this never ever gets triggered :sigh:
if (keyCount == 1) {
Systems::Logging::Log("QueryWithAll should not be used for single key "
"entry! Please use Query for this instead.",
"Dualkey Map", Systems::Logging::LogLevel::Error);
}
// While we don't necessary need the hashes,
// it just helps us tremendously benefit from short circuit checks
Corrade::Containers::Array<std::size_t> keyHashes{keyCount};
for (uint64_t index = 0; index < keyCount; index++) {
keyHashes[index] = std::hash<Key>{}(keys[index]);
}
uint64_t hashToCompare;
Key *keyToCompare;
OppositeKey *oppositeKey;
Containers::Hashmap<OppositeKey, MultiQueryResult<OppositeKey>,
{8.0f, 0.01f, 2.5f, 2048, 8}> // Aggressive hashmap :o
queryResults;
for (DualkeyHash *hash : hashList) {
// Tombstone
if (hash == nullptr) {
continue;
}
// The hell of doing 2 conditions with similar logics in
// the same logical block
if constexpr (searchingInFirstKey) {
hashToCompare = hash->firstKeyHash;
keyToCompare = const_cast<AKey *>(&hash->firstKey);
oppositeKey = const_cast<BKey *>(&hash->secondKey);
} else {
hashToCompare = hash->secondKeyHash;
keyToCompare = const_cast<BKey *>(&hash->secondKey);
oppositeKey = const_cast<AKey *>(&hash->firstKey);
}
// The code above was done to make this code more uniform
for (uint64_t index = 0; index < keyCount; index++) {
if (keyHashes[index] == hashToCompare && keys[index] == *keyToCompare) {
if (queryResults.Has(*oppositeKey)) [[likely]] {
auto &entry = queryResults.Get(*oppositeKey);
entry.valueQueryResults[index] = &hash->value;
++entry.howManyFound;
break;
}
queryResults
.Insert(
*oppositeKey,
{oppositeKey, Corrade::Containers::Array<Value *>{keyCount}})
.valueQueryResults[index] = &hash->value;
}
}
}
return queryResults.ExtractValuesToArray();
}
};
} // namespace Tourmaline::Containers
#endif

View File

@@ -0,0 +1,192 @@
/*
* SPDX-FileCopyrightText: Dora "cat" <cat@thenight.club>
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef GUARD_TOURMALINE_HASHMAP_H
#define GUARD_TOURMALINE_HASHMAP_H
#include "../Concepts.hpp"
#include "../Systems/Logging.hpp"
#include "ContainerOptions.hpp"
#include <cstddef>
#include <vector>
namespace Tourmaline::Containers {
template <Concepts::Hashable Key, typename Value, HashmapOptions Options = {}>
class Hashmap {
public:
Hashmap() { storage.resize(Options.minimumBucketCount); }
~Hashmap() { Clear(); }
Value &Insert(Key key, Value value) {
if (currentLoadFactor >= Options.loadFactor &&
currentlyRehashing == false) {
rehash();
}
std::size_t keyHash = std::hash<Key>{}(key),
keyHashPosition = keyHash % storage.size();
// Empty bucket
if (!storage[keyHashPosition].empty()) {
// Throws
Systems::Logging::Log("Trying to insert the same key twice! Throwing...",
"Hashmap", Systems::Logging::Error, Has(key));
} else {
storage[keyHashPosition].reserve(Options.reservedBucketSpace);
}
storage[keyHashPosition].emplace_back(key, std::move(value), keyHash);
currentLoadFactor = (++count) / static_cast<float>(bucketCount);
return storage[keyHashPosition].back().value;
}
void Remove(const Key &key) {
std::size_t keyHash = std::hash<Key>{}(key),
keyHashPosition = keyHash % storage.size();
// Throws
Systems::Logging::Log("Trying to remove a non-existant key! Throwing...",
"Hashmap", Systems::Logging::Error,
storage[keyHashPosition].empty());
std::erase_if(storage[keyHashPosition],
[keyHash, &key](const hashStorage &hash) {
return hash.hash == keyHash && hash.key == key;
});
currentLoadFactor = (--count) / static_cast<float>(bucketCount);
if (currentLoadFactor <= Options.minimizeFactor) {
rehash();
}
}
[[nodiscard("Unnecessary call of Has function")]]
bool Has(const Key &key) noexcept {
std::size_t keyHash = std::hash<Key>{}(key),
keyHashPosition = keyHash % storage.size();
// Empty bucket
if (storage[keyHashPosition].empty()) {
return false;
}
for (const hashStorage &hash : storage[keyHashPosition]) {
if (hash.hash == keyHash && hash.key == key) {
return true;
}
}
return false;
}
[[nodiscard("Unnecessary call of Get function")]]
Value &Get(const Key &key) {
std::size_t keyHash = std::hash<Key>{}(key),
keyHashPosition = keyHash % storage.size();
Systems::Logging::Log(
"Trying to access a non-existant bucket for a key! Throwing...",
"Hashmap", Systems::Logging::Error, storage[keyHashPosition].empty());
for (hashStorage &hash : storage[keyHashPosition]) {
if (hash.hash == keyHash && hash.key == key) {
return hash.value;
}
}
Systems::Logging::Log("Trying to access a non-existant key! Throwing...",
"Hashmap", Systems::Logging::Error);
throw;
}
[[nodiscard("Discarding an expensive operation!")]]
std::vector<Value> ExtractValuesToArray() {
std::vector<Value> result;
result.reserve(count);
for (bucket &entry : storage) {
for (hashStorage &hash : entry) {
result.emplace_back(std::move(hash.value));
}
entry.clear();
}
count = 0;
bucketCount = Options.minimumBucketCount;
std::vector<bucket> newStorage;
storage.swap(newStorage);
return result;
}
void Clear() noexcept {
storage.clear();
count = 0;
}
[[nodiscard("Unnecessary call of Count function")]]
std::size_t Count() noexcept {
return count;
}
private:
bool rehash(std::size_t goalSize = 0) {
// Minimum
goalSize = goalSize == 0 ? count : goalSize;
float wouldBeLoadFactor = goalSize / static_cast<float>(bucketCount);
if (wouldBeLoadFactor < Options.loadFactor &&
wouldBeLoadFactor > Options.minimizeFactor) [[unlikely]] {
return false; // No rehashing is required
}
// Putting it closer to minimizeFactor
std::size_t goalBucketCount = goalSize / preferredLoadFactor;
if (goalBucketCount < Options.minimumBucketCount) [[unlikely]] {
goalBucketCount = Options.minimumBucketCount;
}
// No need to reallocate
if (goalBucketCount == bucketCount) {
return false;
}
currentlyRehashing = true;
std::vector<bucket> oldStorage = std::move(storage);
storage = std::vector<bucket>();
storage.resize(goalBucketCount);
// Repopulate and cleanup
for (bucket &entry : oldStorage) {
for (hashStorage &hash : entry) {
Insert(std::move(hash.key), std::move(hash.value));
}
entry.clear();
}
// It's necessary to write these again due to insert above
currentLoadFactor = goalSize / static_cast<float>(goalBucketCount);
bucketCount = goalBucketCount;
count = goalSize;
currentlyRehashing = false;
return true;
}
struct hashStorage {
Key key;
Value value;
std::size_t hash;
};
using bucket = std::vector<hashStorage>;
std::vector<bucket> storage;
std::size_t count = 0, bucketCount = Options.minimumBucketCount;
float currentLoadFactor = 0,
preferredLoadFactor = (Options.loadFactor + Options.minimizeFactor) /
Options.leaningFactor;
bool currentlyRehashing = false; // Lock for Insert in rehash
};
} // namespace Tourmaline::Containers
#endif

View File

@@ -10,81 +10,77 @@
#ifndef GUARD_TOURMALINE_ECS_H
#define GUARD_TOURMALINE_ECS_H
#include <any>
#include <concepts>
#include <format>
#include <typeindex>
#include <utility>
#include "../Containers/DualkeyMap.hpp"
#include "../Containers/Hashmap.hpp"
#include "../Types.hpp"
#include "ECS/BuiltinComponents.hpp"
#include "Logging.hpp"
namespace Tourmaline::Systems::ECS {
using Entity = Tourmaline::Type::UUID;
class World;
struct BaseComponent {
public:
virtual ~BaseComponent() = default;
const Entity &GetOwner();
private:
const Entity *owner;
friend World;
};
// Concepts
template <typename T>
concept Component = std::derived_from<T, BaseComponent>;
using System = Tourmaline::Type::UUID;
class World {
public:
// Entity
World() {}
// ====== World controls ======
void Step();
// ======== Entities ========
[[nodiscard]]
Entity CreateEntity();
[[nodiscard("Pointless call of EntityExists")]]
bool EntityExists(const Entity &entity) noexcept;
[[nodiscard("It is not guaranteed that an entity can always be destroyed, "
"please make sure by checking the returned bool")]]
bool DestroyEntity(Entity entity);
// Components
template <Component component, typename... Args>
component &AddComponent(const Entity &entity,
Args &&...constructionArguments) {
auto newComponent = entityComponentMap.insert(
entity, typeid(component), component(constructionArguments...));
// ======== Components ========
template <isAComponent Component, typename... ComponentArgs>
Component &AddComponent(const Entity &entity, ComponentArgs &&...args) {
auto newComponent = entityComponentMap.Insert(entity, typeid(Component),
Component(args...));
return std::any_cast<component &>(std::get<2>(newComponent));
return std::any_cast<Component &>(std::get<2>(newComponent));
}
template <Component component>
[[nodiscard("Discarding an expensive operation's result!")]]
component &GetComponent(const Entity &entity) {
auto result = entityComponentMap.query(entity, typeid(component));
template <isAComponent Component>
[[nodiscard("Pointless call of GetComponent")]]
Component &GetComponent(const Entity &entity) {
auto result = entityComponentMap.Query(entity, typeid(Component));
if (result.empty()) {
Logging::Log(std::format("Entity {} does not have component {}!",
entity.asString(), typeid(component).name()),
"ECS/GetComponent", Logging::LogLevel::Error);
Logging::LogFormatted("Entity {} does not have component {}!",
"ECS/GetComponent", Logging::LogLevel::Error,
entity.asString(), typeid(Component).name());
}
return std::any_cast<component &>(result.begin()->second);
return std::any_cast<Component &>(result.begin()->second);
}
template <Component component>
[[nodiscard("Discarding an expensive operation's result!")]]
template <isAComponent Component>
[[nodiscard("Pointless call of HasComponent")]]
bool HasComponent(const Entity &entity) {
// TO BE IMPLEMENTED
return true;
return entityComponentMap.Query(entity, typeid(Component)).size();
}
template <Component component>
[[nodiscard("It is not guaranteed that a component can always be removed, "
"please make sure by checking the returned bool")]]
bool RemoveComponent(const Entity &entity) {
return entityComponentMap.remove(entity, typeid(component));
template <isAComponent Component> bool RemoveComponent(const Entity &entity) {
return entityComponentMap.Remove(entity, typeid(Component));
}
// Copying is not allowed since the ECS world is meant to be
// a session with its own private session sensitive variables
World(const World &) = delete;
World &operator=(const World &) = delete;
private:
Tourmaline::Containers::DualkeyMap<Entity, std::type_index, std::any>
using systemFunction =
std::function<void(const Entity &, std::span<std::any *>)>;
Containers::DualkeyMap<Entity, std::type_index, std::any>
entityComponentMap{};
Containers::Hashmap<System, systemFunction> registeredSystems{};
// ======== Life-cycle ========
void preSystems();
void postSystems();
};
} // namespace Tourmaline::Systems::ECS
#endif

View File

@@ -7,11 +7,24 @@
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "../ECS.hpp"
#ifndef GUARD_TOURMALINE_BUILTIN_COMPONENTS_H
#define GUARD_TOURMALINE_BUILTIN_COMPONENTS_H
#include <concepts>
namespace Tourmaline::Systems::ECS {
struct Component {
public:
virtual ~Component() = default;
};
template <typename T>
concept isAComponent = std::derived_from<T, ECS::Component>;
} // namespace Tourmaline::Systems::ECS
namespace Tourmaline::Systems::Components {
struct Position : public Tourmaline::Systems::ECS::BaseComponent {
Position(double x = 0, double y = 0, double z = 0) : x(x), y(y), z(z) {}
double x, y, z;
// Builtin
struct Base : public ECS::Component {
double x = 0, y = 0, z = 0;
};
} // namespace Tourmaline::Systems::Components
#endif

View File

@@ -8,31 +8,36 @@
*/
#ifndef GUARD_TOURMALINE_LOGGING_H
#define GUARD_TOURMALINE_LOGGING_H
#include <array>
#include "Corrade/Containers/String.h"
#include "Corrade/Containers/StringView.h"
#include "Corrade/Utility/Format.h"
#include <fstream>
#include <string>
namespace Tourmaline::Systems {
class Logging {
public:
enum class LogLevel {
Critical = 0,
Error = 1,
Warning = 2,
Info = 3,
Debug = 4,
Trace = 5
};
enum LogLevel { Critical, Error, Warning, Info, Debug, Trace };
static void LogToFile(std::string File = "");
static void Log(const std::string &message,
const std::string &position = "Unknown",
static void LogToFile(Corrade::Containers::String File = "");
static void Log(Corrade::Containers::StringView message,
Corrade::Containers::StringView position = "Unknown",
LogLevel severity = LogLevel::Info, bool assertion = true);
template <class... Args>
static void LogFormatted(const char *format, const char *position,
LogLevel severity, const Args &...args) {
static Corrade::Containers::String output{Corrade::ValueInit, 4096};
std::size_t size = Corrade::Utility::formatInto(output, format, args...);
Log(Corrade::Containers::StringView{output.begin(), size}, position,
severity);
}
private:
static std::fstream File;
static std::array<std::pair<const std::string, const std::string>, 6>
LogLevelToString;
static const char *LogLevelToColour[Trace + 1];
static const char *LogLevelToString[Trace + 1];
};
} // namespace Tourmaline::Systems
#endif

View File

@@ -10,7 +10,7 @@
#ifndef GUARD_TOURMALINE_RANDOM_H
#define GUARD_TOURMALINE_RANDOM_H
#include "../Types.hpp"
#include <TourmalineExternal/random/xoshiro.h>
#include "TourmalineExternal/random/xoshiro.h"
#include <type_traits>

View File

@@ -6,33 +6,29 @@
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef GUARD_TOURMALINE_TYPES_H
#define GUARD_TOURMALINE_TYPES_H
#include "Corrade/Containers/String.h"
#include "TourmalineExternal/random/xoshiro.h"
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
namespace Tourmaline::Type {
class UUID {
public:
constexpr static uint8_t BitLength = 128;
constexpr static uint8_t QWORDLength = BitLength / 64;
constexpr static uint8_t ByteLength = BitLength / 8;
[[nodiscard]]
std::string asString() const;
Corrade::Containers::String asString() const;
bool operator==(const UUID &rhs) const;
UUID(uint64_t firstHalf, uint64_t secondHalf);
UUID(const std::string &uuid);
UUID(const UUID &uuid);
UUID(UUID &&uuid) noexcept;
UUID &operator=(const UUID &uuid);
UUID &operator=(UUID &&uuid);
~UUID() = default;
private:
std::unique_ptr<uint64_t[]> data = std::make_unique<uint64_t[]>(QWORDLength);
uint64_t firstHalf = 0, secondHalf = 0;
friend struct std::hash<Tourmaline::Type::UUID>;
};
} // namespace Tourmaline::Type
@@ -40,10 +36,9 @@ private:
namespace std {
template <> struct hash<Tourmaline::Type::UUID> {
size_t operator()(const Tourmaline::Type::UUID &uuid) const noexcept {
const auto data = uuid.data.get();
size_t h1 = std::hash<uint64_t>{}(data[0]),
h2 = std::hash<uint64_t>{}(data[1]);
return h1 ^ (h2 << 1);
uint64_t hash = Xoshiro::splitmix64(uuid.firstHalf);
hash += uuid.secondHalf;
return Xoshiro::splitmix64(hash);
}
};

View File

@@ -7,8 +7,7 @@
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <Systems/ECS.hpp>
#include "Systems/ECS.hpp"
#include "Systems/ECS/BuiltinComponents.hpp"
using namespace Tourmaline::Systems::ECS;
const Entity &BaseComponent::GetOwner() { return *this->owner; }
// Empty until future use

View File

@@ -7,28 +7,52 @@
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <Systems/ECS.hpp>
#include <Systems/ECS/BuiltinComponents.hpp>
#include <Systems/Random.hpp>
#include <optional>
#include "Systems/ECS.hpp"
#include "Systems/ECS/BuiltinComponents.hpp"
#include "Systems/Random.hpp"
using namespace Tourmaline::Systems::ECS;
using namespace Tourmaline::Systems;
using namespace ECS;
// It is preferable to send a copy of the UUID instead of reference since
// the entity itself may be destroyed in the memory
void World::Step() {
preSystems();
// Actual systems will happen here
postSystems();
}
void World::preSystems() {
// Defined for future use
}
void World::postSystems() {
// Defined for future use
}
// Entities
Entity World::CreateEntity() {
auto newEntity = entityComponentMap.insert(
Random::GenerateUUID(), typeid(Tourmaline::Systems::Components::Position),
Tourmaline::Systems::Components::Position());
auto newEntity = Random::GenerateUUID();
return Entity(std::get<0>(newEntity));
// Default components
entityComponentMap.Insert(newEntity, typeid(Components::Base),
Components::Base());
return newEntity;
}
bool World::EntityExists(const Entity &entity) noexcept {
// TO BE IMPLEMENTED
bool exists = false;
entityComponentMap.Scan(
[&exists, entity](const Tourmaline::Type::UUID &currentEntity,
const std::type_index &, std::any &) -> bool {
if (currentEntity == entity) {
exists = true;
return true;
}
return false;
});
return exists;
}
bool World::DestroyEntity(Entity entity) {
return entityComponentMap.remove(entity, std::nullopt);
return entityComponentMap.Remove(entity, std::nullopt);
}

View File

@@ -7,62 +7,77 @@
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <Systems/Logging.hpp>
#include "Systems/Logging.hpp"
#include "Corrade/Containers/String.h"
#include "Corrade/Containers/StringView.h"
#include "Corrade/Tags.h"
#include "Corrade/Utility/Format.h"
#include <cerrno>
#include <chrono>
#include <cstddef>
#include <cstring>
#include <exception>
#include <format>
#include <fstream>
#include <print>
#include <stdexcept>
#include <string>
#include <utility>
#include <string_view>
using namespace Tourmaline::Systems;
using namespace Corrade::Containers;
using namespace Corrade::Utility;
// This is what happens when it takes you 50 years to implement
// reflections to a language
std::array<std::pair<const std::string, const std::string>, 6>
Logging::LogLevelToString{std::pair{"Critical", "[0;31m"},
{"Error", "[0;91m"},
{"Warning", "[0;33m"},
{"Info", "[0;37m"},
{"Debug", "[0;92m"},
{"Trace", "[0;36m"}};
const char *Logging::LogLevelToColour[Logging::Trace + 1]{
"[0;31m", "[0;91m", "[0;33m", "[0;37m", "[0;92m", "[0;36m"};
const char *Logging::LogLevelToString[Logging::Trace + 1]{
"Critical", "Error", "Warning", "Info", "Debug", "Trace"};
std::fstream Logging::File;
void Logging::LogToFile(std::string File) {
void Logging::LogToFile(String File) {
if (File == "") {
const auto now = std::chrono::system_clock::now();
File = std::format("Tourmaline-{:%Y-%j}.txt", now);
std::chrono::year_month_day ymd{std::chrono::floor<std::chrono::days>(now)};
File = String{Corrade::ValueInit, 128};
formatInto(File, "Tourmaline-{}-{}-{}.txt", static_cast<int>(ymd.year()),
static_cast<unsigned>(ymd.month()),
static_cast<unsigned>(ymd.day()));
}
Logging::File.open(File, std::fstream::out);
Logging::File.open(File.data(), std::fstream::out);
if (Logging::File.fail()) {
throw std::runtime_error("FAILED! Could not open or create the file: " +
File + "!\n" + strerror(errno));
String error =
format("FAILED! Could not open or create the file: {}! Error: {}", File,
strerror(errno));
throw std::runtime_error(error.data());
}
}
void Logging::Log(const std::string &message, const std::string &position,
void Logging::Log(StringView message, StringView position,
Logging::LogLevel severity, bool assertion) {
if (assertion) [[likely]] {
auto loglevelData =
Logging::LogLevelToString[static_cast<size_t>(severity)];
std::string output =
std::format("[{}@{}] {}\n", loglevelData.first, position, message);
if (assertion) {
static String output{Corrade::ValueInit,
4096}; // This is done to stop allocations
std::size_t formattedSize = formatInto(
output, "[{}@{}] {}\n", LogLevelToString[severity], position, message);
std::print("\033{} {}\033[0m", loglevelData.second, output);
std::print(
"\033{} {}\033[0m", LogLevelToColour[severity],
std::string_view{output.begin(), output.begin() + formattedSize});
if (Logging::File.is_open()) {
Logging::File.write(output.c_str(), output.size());
Logging::File.write(output.data(), formattedSize);
Logging::File.flush(); // Terrible but necessary sadly
}
// Error and Critical
if (severity < Logging::LogLevel::Warning) {
throw std::runtime_error(output);
if (severity == Logging::LogLevel::Error) {
throw std::runtime_error(output.data());
}
if (severity == Logging::LogLevel::Critical) {
std::terminate();
}
}
}

View File

@@ -6,7 +6,8 @@
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <Systems/Random.hpp>
#include "Systems/Random.hpp"
#include <bit>
#include <cstdint>

View File

@@ -7,63 +7,35 @@
* obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <Types.hpp>
#include "Corrade/Utility/Format.h"
#include "Types.hpp"
#include <charconv>
#include <cstdint>
#include <cstring>
#include <format>
#include <memory>
#include <string>
using namespace Tourmaline::Type;
std::string UUID::asString() const {
return std::format("{:016X}{:016X}", data[0], data[1]);
using namespace Corrade::Containers;
using namespace Corrade::Utility;
String UUID::asString() const {
return format("{:.16X}{:.16X}", firstHalf, secondHalf);
}
bool UUID::operator==(const UUID &rhs) const {
// Since size may be increased
for (uint8_t index = 0; index < QWORDLength; index++) {
if (this->data[index] != rhs.data[index]) {
return false;
}
}
return true;
return firstHalf == rhs.firstHalf && secondHalf == rhs.secondHalf;
}
UUID::UUID(const UUID &uuid) {
std::memcpy(data.get(), uuid.data.get(), UUID::ByteLength);
}
UUID &UUID::operator=(const UUID &uuid) {
if (this != &uuid) [[likely]] {
std::memcpy(data.get(), uuid.data.get(), UUID::ByteLength);
}
return *this;
}
UUID &UUID::operator=(UUID &&uuid) {
if (this != &uuid) [[likely]] {
data.swap(uuid.data);
}
return *this;
}
UUID::UUID(UUID &&uuid) noexcept { data.swap(uuid.data); }
UUID::UUID(uint64_t firstHalf, uint64_t secondHalf) {
data[0] = firstHalf;
data[1] = secondHalf;
}
UUID::UUID(uint64_t firstHalf, uint64_t secondHalf)
: firstHalf(firstHalf), secondHalf(secondHalf) {}
UUID::UUID(const std::string &uuid) {
// We are assuming that it is a valid UUID, if not then somewhere else this
// UUID should cause an error
auto start = uuid.c_str(), half = start + ByteLength,
tail = half + ByteLength; // Each UUID element is 16 characters padded
auto start = uuid.c_str(), half = start + 16,
tail = half + 16; // Each UUID element is 16 characters padded
std::from_chars(start, half, data[0], 16);
std::from_chars(half, tail, data[1], 16);
std::from_chars(start, half, firstHalf, 16);
std::from_chars(half, tail, secondHalf, 16);
}