Compare commits

...

18 Commits

Author SHA1 Message Date
cat
dda6e25bf9 Added MPL-2.0 to CMakeLists.txt 2026-01-09 01:01:19 +02:00
cat
518995ddd1 Restructuring 2026-01-09 00:23:37 +02:00
cat
e8498f9e66 Added colours to logging by default 2026-01-08 21:47:10 +02:00
cat
88f5d02b6a Minor tweaks on the general codebase 2026-01-06 04:10:58 +02:00
cat
31ebef777b Added a basic CMAKE script (no find library or pkg-config yet!) 2026-01-06 03:10:55 +02:00
cat
cb9005bf80 Tweaked Xoshiro.h to fix a warn 2026-01-06 03:10:34 +02:00
cat
b457ecce47 Restructing on the project 2026-01-06 03:10:27 +02:00
cat
e64b8ed194 renaming sourceCode to source 2026-01-06 02:28:00 +02:00
cat
c1efee5b4e Created 2/3rd of ECS (EC has been added) and World for ECS 2026-01-06 01:46:03 +02:00
cat
c36c3468bb Added warn as a log level, also made error and critical throw 2026-01-06 01:31:00 +02:00
cat
6d9edc18b4 Tweaked UUID for hashing 2026-01-06 01:30:09 +02:00
cat
8174c19f00 Added Logging and tweaked Systems 2026-01-04 22:34:17 +02:00
cat
89ebe63e4b Updated README.md 2026-01-04 03:03:17 +02:00
cat
3b99a9d913 Updated gitignore 2026-01-04 02:14:55 +02:00
cat
e968585f5b Added Rule of five to UUID 2026-01-04 02:13:38 +02:00
cat
6b5213d605 UUID system iteration 2 2026-01-03 13:23:23 +02:00
cat
7470f01dc4 Added Random system and UUIDv4 generation 2026-01-02 21:42:35 +02:00
cat
198b095c0e Added xoshiro256 as the psudo random generator 2026-01-02 18:41:15 +02:00
14 changed files with 1010 additions and 4 deletions

16
.clangd Normal file
View File

@@ -0,0 +1,16 @@
{
"CompileFlags": {
"Add": [
"-std=c++23",
"-Wall",
"-Wextra",
"-Wpedantic",
]
},
"Index": {
"Background": "Build"
},
"Completion": {
"AllScopes": true
}
}

3
.gitignore vendored
View File

@@ -162,3 +162,6 @@ build-iPhoneSimulator/
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
# .rubocop-https?--*
# Custom
# For personal testing cases
test/

39
CMakeLists.txt Normal file
View File

@@ -0,0 +1,39 @@
# 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/.
cmake_minimum_required(VERSION 3.10)
project(Tourmaline VERSION 1)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address")
endif()
include(GNUInstallDirs)
include_directories(headers)
add_library(${PROJECT_NAME} SHARED
"source/Systems/ECS/Component.cpp"
"source/Systems/ECS/World.cpp"
"source/Systems/Logging.cpp"
"source/Systems/Random.cpp"
"source/Types/UUID.cpp")
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
# Nothing to link right now
target_link_libraries(${PROJECT_NAME})
install(
TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin)
install(DIRECTORY headers/ DESTINATION include/${PROJECT_NAME})

View File

@@ -1,9 +1,30 @@
# Tourmaline Engine
Tormaline Engine is a game engine created for game development with C++23. For its stack it uses Corrade as its STD replacement (when applicable), Magnum Graphics as its graphics engine, miniaudio as its sound engine, FLECS as its main ECS controller.
Tormaline Engine is a game engine created for game development with C++23.
The engine aims to incorporate mruby to make ruby its optional scripting language. The goal is to make 1 to 1 parity with C++ and Ruby scirpting. So a game for example can use Ruby for its basic player controls or networking, while using C++ for its complex AI logic. A game is not strictly required to use C++ and Ruby. A game can choose how much of either language it needs.
A lot of the info regarding Tourmaline has been removed from here. This is due to project still being in early phases of development.
Tourmaline Engine will be ECS only by default. ECS will be strictly imposed on engine level. Tourmaline Engine aside from ECS tries not to force the developer into its (my own) opinionated way of doing things. This is achieved by allowing the programmer to easily build modules to the engine. We expose module developers majority of the low level aspects of the project.
# 3rd Party Used 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
Tourmaline Engine places an emphasis on good documentation, plenty of examples, and tutorials. Additionally Tourmaline Engine targets to have a large standard implementation for most day to day game development needs. Physics, shadows, sounds, video playback, OS agnostic IO interactions, etc...
# 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.
From [Find Gemstone](https://www.findgemstone.com/blog/what-is-tourmaline-used-for-in-industry/)
> 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)" )

146
headers/Systems/ECS.hpp Normal file
View File

@@ -0,0 +1,146 @@
/*
* 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_ECS_H
#define GUARD_TOURMALINE_ECS_H
#include <any>
#include <concepts>
#include <format>
#include <typeindex>
#include <unordered_map>
#include <utility>
#include "../Types.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>;
class World {
public:
// Entity
Entity CreateEntity();
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 T, typename... Args>
T &AddComponent(const Entity &entity, Args &&...constructionArguments) {
auto entityIter = GetEntityIterator(
entity,
std::format(
"Cannot add component \"{}\"! Entity \"{}\" does not exist!",
typeid(T).name(), entity.asString()),
"AddComponent", Systems::Logging::LogLevel::Error);
auto [componentIter, success] = entityIter->second.try_emplace(
typeid(T), T(std::forward<Args>(constructionArguments)...));
Systems::Logging::Log(
std::format("Cannot add component! Component \"{}\" already exists "
"in entity \"{}\" ",
typeid(T).name(), entity.asString()),
"AddComponent", Systems::Logging::LogLevel::Error, !success);
T &component = std::any_cast<T &>(componentIter->second);
component.owner = &entity;
return component;
}
template <Component T>
[[nodiscard("Discarding an expensive operation's result!")]]
T &GetComponent(const Entity &entity) {
auto iter = GetEntityIterator(
entity,
std::format("Can't get entity \"{}\"'s component \"{}\", since "
"entity does not exist!",
entity.asString(), typeid(T).name()),
"GetComponent", Systems::Logging::LogLevel::Error);
auto component = iter->second.find(typeid(T));
Systems::Logging::Log(
std::format(
"Entity \"{}\" does not have component \"{}\", cannot get it!",
entity.asString(), typeid(T).name()),
"GetComponent", Systems::Logging::LogLevel::Error,
component == iter->second.end());
return std::any_cast<T &>(component->second);
}
template <Component T>
[[nodiscard("Discarding an expensive operation's result!")]]
bool HasComponent(const Entity &entity) {
auto iter = GetEntityIterator(
entity,
std::format("Can't find if entity \"{}\" has component \"{}\", since "
"entity does not exist!",
entity.asString(), typeid(T).name()));
return iter != entityComponentList.end() &&
(iter->second.find(typeid(T)) != iter->second.end());
}
template <Component T>
[[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) {
auto entityIter = GetEntityIterator(
entity,
std::format("Cannot remove component {} from entity {}, since entity "
"does not exist!",
typeid(T).name(), entity.asString()),
"RemoveComponent", Systems::Logging::LogLevel::Warning);
if (entityIter == entityComponentList.end()) {
return false;
}
auto componentIter = entityIter->second.find(typeid(T));
if (componentIter == entityIter->second.end()) {
Systems::Logging::Log(
std::format("Cannot remove component {} from entity {}, since entity "
"does not have that component",
typeid(T).name(), entity.asString()),
"RemoveComponent", Systems::Logging::LogLevel::Warning);
return false;
}
entityIter->second.erase(componentIter);
return true;
}
private:
std::unordered_map<Entity, std::unordered_map<std::type_index, std::any>>
entityComponentList{};
decltype(entityComponentList)::iterator
GetEntityIterator(const Entity &entity, const std::string &errorMessage = "",
const std::string &position = "",
Tourmaline::Systems::Logging::LogLevel severity =
Systems::Logging::LogLevel::Warning);
};
} // namespace Tourmaline::Systems::ECS
#endif

View File

@@ -0,0 +1,38 @@
/*
* 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_LOGGING_H
#define GUARD_TOURMALINE_LOGGING_H
#include <array>
#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
};
static void LogToFile(std::string File = "");
static void Log(const std::string &message,
const std::string &position = "Unknown",
LogLevel severity = LogLevel::Info, bool assertion = true);
private:
static std::fstream File;
static std::array<std::pair<const std::string, const std::string>, 6>
LogLevelToString;
};
} // namespace Tourmaline::Systems
#endif

View File

@@ -0,0 +1,30 @@
/*
* 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_RANDOM_H
#define GUARD_TOURMALINE_RANDOM_H
#include "../../libraries/random/xoshiro.h"
#include "../Types.hpp"
#include <type_traits>
namespace Tourmaline::Systems {
class Random {
public:
template <typename T>
requires std::is_integral_v<T>
static T Generate(T max, T min = 0) {
return (generator() % (max - min + 1)) + min;
}
static Tourmaline::Type::UUID GenerateUUID();
private:
static Xoshiro::Xoshiro256PP generator;
};
} // namespace Tourmaline::Systems
#endif

52
headers/Types.hpp Normal file
View File

@@ -0,0 +1,52 @@
/*
* 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_TYPES_H
#define GUARD_TOURMALINE_TYPES_H
#include <cstdint>
#include <functional>
#include <memory>
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;
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);
friend struct std::hash<Tourmaline::Type::UUID>;
};
} // namespace Tourmaline::Type
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);
}
};
} // namespace std
#endif

428
libraries/random/xoshiro.h Normal file
View File

@@ -0,0 +1,428 @@
/* Code was taken from here:
https://prng.di.unimi.it
And wrapped as a bit generator for C++'s random module' */
/* Written in 2019 by David Blackman and Sebastiano Vigna (vigna@acm.org)
To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
See <http://creativecommons.org/publicdomain/zero/1.0/>. */
#ifndef XOSHIRO_H
#define XOSHIRO_H
#include <iostream>
#include <cstring>
#include <cstdint>
namespace Xoshiro {
#if __cplusplus >= 202001L
# define HAS_CPP20
#endif
static inline std::uint64_t rotl64(const std::uint64_t x, const int k) {
return (x << k) | (x >> (64 - k));
}
static inline std::uint32_t rotl32(const std::uint32_t x, const int k) {
return (x << k) | (x >> (32 - k));
}
/* these are in order to avoid gcc warnings about 'strict aliasing rules' */
static inline std::uint32_t extract_32bits_from64_left(const std::uint64_t x)
{
std::uint32_t out;
std::memcpy(&out, reinterpret_cast<const std::uint32_t*>(&x), sizeof(std::uint32_t));
return out;
}
static inline std::uint32_t extract_32bits_from64_right(const std::uint64_t x)
{
std::uint32_t out;
std::memcpy(&out, reinterpret_cast<const std::uint32_t*>(&x) + 1, sizeof(std::uint32_t));
return out;
}
static inline void assign_32bits_to64_left(std::uint64_t assign_to, const std::uint32_t take_from)
{
std::memcpy(reinterpret_cast<std::uint32_t*>(&assign_to), &take_from, sizeof(std::uint32_t));
}
static inline void assign_32bits_to64_right(std::uint64_t assign_to, const std::uint32_t take_from)
{
std::memcpy(reinterpret_cast<std::uint32_t*>(&assign_to) + 1, &take_from, sizeof(std::uint32_t));
}
constexpr static const uint64_t JUMP_X256PP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c };
constexpr static const uint64_t LONG_JUMP_X256PP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 };
constexpr static const uint32_t JUMP_X128PP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b };
constexpr static const uint32_t LONG_JUMP_X128PP[] = { 0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662 };
template <class int_t, class rng_t>
static inline void jump_state(const int_t jump_table[4], rng_t &rng)
{
int_t s0 = 0;
int_t s1 = 0;
int_t s2 = 0;
int_t s3 = 0;
for (int i = 0; i < 4; i++)
{
for (int b = 0; b < static_cast<int>(8*sizeof(int_t)); b++) // static_cast has been added by Tourmaline Engine!
{
if (jump_table[i] & ((int_t)1) << b)
{
s0 ^= rng.state[0];
s1 ^= rng.state[1];
s2 ^= rng.state[2];
s3 ^= rng.state[3];
}
rng();
}
}
rng.state[0] = s0;
rng.state[1] = s1;
rng.state[2] = s2;
rng.state[3] = s3;
}
/* This is a fixed-increment version of Java 8's SplittableRandom generator
See http://dx.doi.org/10.1145/2714064.2660195 and
http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html
It is a very fast generator passing BigCrush, and it can be useful if
for some reason you absolutely want 64 bits of state. */
static inline std::uint64_t splitmix64(const std::uint64_t seed)
{
std::uint64_t z = (seed + 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}
/* This is xoshiro256++ 1.0, one of our all-purpose, rock-solid generators.
It has excellent (sub-ns) speed, a state (256 bits) that is large
enough for any parallel application, and it passes all tests we are
aware of.
For generating just floating-point numbers, xoshiro256+ is even faster.
The state must be seeded so that it is not everywhere zero. If you have
a 64-bit seed, we suggest to seed a splitmix64 generator and use its
output to fill s. */
class Xoshiro256PP
{
public:
using result_type = std::uint64_t;
std::uint64_t state[4] = {0x3d23dce41c588f8c, 0x10c770bb8da027b0, 0xc7a4c5e87c63ba25, 0xa830f83239465a2e};
constexpr static result_type min()
{
return 0;
}
constexpr static result_type max()
{
return UINT64_MAX;
}
Xoshiro256PP() = default;
~Xoshiro256PP() noexcept = default;
Xoshiro256PP(Xoshiro256PP &other) = default;
Xoshiro256PP& operator=(const Xoshiro256PP &other) = default;
Xoshiro256PP(Xoshiro256PP &&) noexcept = default;
Xoshiro256PP& operator=(Xoshiro256PP &&) noexcept = default;
void seed(const std::uint64_t seed)
{
this->state[0] = splitmix64(splitmix64(seed));
this->state[1] = splitmix64(this->state[0]);
this->state[2] = splitmix64(this->state[1]);
this->state[3] = splitmix64(this->state[2]);
}
void seed(const std::uint64_t seed[4])
{
std::memcpy(this->state, seed, 4*sizeof(std::uint64_t));
}
template<class Sseq>
void seed(Sseq& seq)
{
seq.generate(reinterpret_cast<std::uint32_t*>(&this->state[0]),
reinterpret_cast<std::uint32_t*>(&this->state[0] + 4));
}
Xoshiro256PP(const std::uint64_t seed)
{
this->seed(seed);
}
Xoshiro256PP(const std::uint64_t seed[4])
{
this->seed(seed);
}
template<class Sseq>
Xoshiro256PP(Sseq& seq)
{
this->seed(seq);
}
result_type operator()()
{
const std::uint64_t result = rotl64(this->state[0] + this->state[3], 23) + this->state[0];
const std::uint64_t t = this->state[1] << 17;
this->state[2] ^= this->state[0];
this->state[3] ^= this->state[1];
this->state[1] ^= this->state[2];
this->state[0] ^= this->state[3];
this->state[2] ^= t;
this->state[3] = rotl64(this->state[3], 45);
return result;
}
void discard(unsigned long long z)
{
for (unsigned long long ix = 0; ix < z; ix++)
this->operator()();
}
Xoshiro256PP jump()
{
Xoshiro256PP new_gen = *this;
jump_state(JUMP_X256PP, new_gen);
return new_gen;
}
Xoshiro256PP long_jump()
{
Xoshiro256PP new_gen = *this;
jump_state(LONG_JUMP_X256PP, new_gen);
return new_gen;
}
bool operator==(const Xoshiro256PP &rhs)
{
return std::memcmp(this->state, rhs.state, 4*sizeof(std::uint64_t));
}
#ifndef HAS_CPP20
bool operator!=(const Xoshiro256PP &rhs)
{
return !std::memcmp(this->state, rhs.state, 4*sizeof(std::uint64_t));
}
#endif
template< class CharT, class Traits >
friend std::basic_ostream<CharT,Traits>&
operator<<(std::basic_ostream<CharT,Traits>& ost, const Xoshiro256PP& e)
{
ost.write(reinterpret_cast<const char*>(&e.state[0]), sizeof(std::uint64_t));
ost.put(' ');
ost.write(reinterpret_cast<const char*>(&e.state[1]), sizeof(std::uint64_t));
ost.put(' ');
ost.write(reinterpret_cast<const char*>(&e.state[2]), sizeof(std::uint64_t));
ost.put(' ');
ost.write(reinterpret_cast<const char*>(&e.state[3]), sizeof(std::uint64_t));
return ost;
}
template< class CharT, class Traits >
friend std::basic_istream<CharT,Traits>&
operator>>(std::basic_istream<CharT,Traits>& ist, Xoshiro256PP& e)
{
ist.read(reinterpret_cast<char*>(&e.state[0]), sizeof(std::uint64_t));
ist.get();
ist.read(reinterpret_cast<char*>(&e.state[1]), sizeof(std::uint64_t));
ist.get();
ist.read(reinterpret_cast<char*>(&e.state[2]), sizeof(std::uint64_t));
ist.get();
ist.read(reinterpret_cast<char*>(&e.state[3]), sizeof(std::uint64_t));
return ist;
}
};
/* This is xoshiro128++ 1.0, one of our 32-bit all-purpose, rock-solid
generators. It has excellent speed, a state size (128 bits) that is
large enough for mild parallelism, and it passes all tests we are aware
of.
For generating just single-precision (i.e., 32-bit) floating-point
numbers, xoshiro128+ is even faster.
The state must be seeded so that it is not everywhere zero. */
class Xoshiro128PP
{
public:
using result_type = std::uint32_t;
std::uint32_t state[4] = {0x1c588f8c, 0x3d23dce4, 0x8da027b0, 0x10c770bb};
constexpr static result_type min()
{
return 0;
}
constexpr static result_type max()
{
return UINT32_MAX;
}
Xoshiro128PP() = default;
~Xoshiro128PP() noexcept = default;
Xoshiro128PP(Xoshiro128PP &other) = default;
Xoshiro128PP& operator=(const Xoshiro128PP &other) = default;
Xoshiro128PP(Xoshiro128PP &&) noexcept = default;
Xoshiro128PP& operator=(Xoshiro128PP &&) noexcept = default;
void seed(const std::uint64_t seed)
{
const auto t1 = splitmix64(seed);
const auto t2 = splitmix64(t1);
this->state[0] = splitmix64(extract_32bits_from64_left(t1));
this->state[1] = splitmix64(extract_32bits_from64_right(t1));
this->state[2] = splitmix64(extract_32bits_from64_left(t2));
this->state[3] = splitmix64(extract_32bits_from64_right(t2));
}
void seed(const std::uint32_t seed)
{
std::uint64_t temp;
assign_32bits_to64_left(temp, seed);
assign_32bits_to64_right(temp, seed);
this->seed(temp);
}
void seed(const std::uint64_t seed[2])
{
std::memcpy(this->state, seed, 4*sizeof(std::uint32_t));
}
void seed(const std::uint32_t seed[4])
{
std::memcpy(this->state, seed, 4*sizeof(std::uint32_t));
}
template<class Sseq>
void seed(Sseq& seq)
{
seq.generate(&this->state[0], &this->state[0] + 4);
}
Xoshiro128PP(const std::uint32_t seed)
{
this->seed(seed);
}
Xoshiro128PP(const std::uint64_t seed)
{
this->seed(seed);
}
Xoshiro128PP(const std::uint32_t seed[4])
{
this->seed(seed);
}
Xoshiro128PP(const std::uint64_t seed[2])
{
this->seed(seed);
}
template<class Sseq>
Xoshiro128PP(Sseq& seq)
{
this->seed(seq);
}
result_type operator()()
{
const std::uint32_t result = rotl32(this->state[0] + this->state[3], 7) + this->state[0];
const std::uint32_t t = this->state[1] << 9;
this->state[2] ^= this->state[0];
this->state[3] ^= this->state[1];
this->state[1] ^= this->state[2];
this->state[0] ^= this->state[3];
this->state[2] ^= t;
this->state[3] = rotl32(this->state[3], 11);
return result;
}
Xoshiro128PP jump()
{
Xoshiro128PP new_gen = *this;
jump_state(JUMP_X128PP, new_gen);
return new_gen;
}
Xoshiro128PP long_jump()
{
Xoshiro128PP new_gen = *this;
jump_state(LONG_JUMP_X128PP, new_gen);
return new_gen;
}
void discard(unsigned long long z)
{
for (unsigned long long ix = 0; ix < z; ix++)
this->operator()();
}
bool operator==(const Xoshiro128PP &rhs)
{
return std::memcmp(this->state, rhs.state, 4*sizeof(std::uint32_t));
}
#ifndef HAS_CPP20
bool operator!=(const Xoshiro128PP &rhs)
{
return !std::memcmp(this->state, rhs.state, 4*sizeof(std::uint32_t));
}
#endif
template< class CharT, class Traits >
friend std::basic_ostream<CharT,Traits>&
operator<<(std::basic_ostream<CharT,Traits>& ost, const Xoshiro128PP& e)
{
ost.write(reinterpret_cast<const char*>(&e.state[0]), sizeof(std::uint32_t));
ost.put(' ');
ost.write(reinterpret_cast<const char*>(&e.state[1]), sizeof(std::uint32_t));
ost.put(' ');
ost.write(reinterpret_cast<const char*>(&e.state[2]), sizeof(std::uint32_t));
ost.put(' ');
ost.write(reinterpret_cast<const char*>(&e.state[3]), sizeof(std::uint32_t));
return ost;
}
template< class CharT, class Traits >
friend std::basic_istream<CharT,Traits>&
operator>>(std::basic_istream<CharT,Traits>& ist, Xoshiro128PP& e)
{
ist.read(reinterpret_cast<char*>(&e.state[0]), sizeof(std::uint32_t));
ist.get();
ist.read(reinterpret_cast<char*>(&e.state[1]), sizeof(std::uint32_t));
ist.get();
ist.read(reinterpret_cast<char*>(&e.state[2]), sizeof(std::uint32_t));
ist.get();
ist.read(reinterpret_cast<char*>(&e.state[3]), sizeof(std::uint32_t));
return ist;
}
};
}
#endif

View File

@@ -0,0 +1,14 @@
/*
* 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/.
*/
#include "../../../headers/Systems/ECS.hpp"
using namespace Tourmaline::Systems::ECS;
const Entity &BaseComponent::GetOwner() { return *this->owner; }

View File

@@ -0,0 +1,55 @@
/*
* 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/.
*/
#include "../../../headers/Systems/ECS.hpp"
#include "../../../headers/Systems/Random.hpp"
using namespace Tourmaline::Systems::ECS;
Entity World::CreateEntity() {
auto [iterator, success] =
entityComponentList.try_emplace(Systems::Random::GenerateUUID());
Systems::Logging::Log("Failed to create an entity! Possibly by incredible "
"luck generated already existing UUID?",
"CreateEntity", Systems::Logging::LogLevel::Critical,
!success);
return iterator->first;
}
bool World::EntityExists(const Entity &entity) noexcept {
return entityComponentList.find(entity) != entityComponentList.end();
}
bool World::DestroyEntity(Entity entity) {
auto entityIter = GetEntityIterator(
entity,
std::format("Cannot delete entity \"{}\", it does not exist!",
entity.asString()),
"DestroyEntity");
if (entityIter == entityComponentList.end()) {
return false;
}
entityComponentList.erase(entityIter);
return true;
}
// Very repetitive code
decltype(World::entityComponentList)::iterator
World::GetEntityIterator(const Entity &entity, const std::string &errorMessage,
const std::string &position,
Tourmaline::Systems::Logging::LogLevel severity) {
auto iter = entityComponentList.find(entity);
Systems::Logging::Log(errorMessage, "GetEntityIterator/" + position, severity,
iter == entityComponentList.end());
return iter;
}

View File

@@ -0,0 +1,68 @@
/*
* 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/.
*/
#include "../../headers/Systems/Logging.hpp"
#include <cerrno>
#include <chrono>
#include <cstddef>
#include <cstring>
#include <format>
#include <fstream>
#include <print>
#include <stdexcept>
#include <string>
#include <utility>
using namespace Tourmaline::Systems;
// 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"}};
std::fstream Logging::File;
void Logging::LogToFile(std::string File) {
if (File == "") {
const auto now = std::chrono::system_clock::now();
File = std::format("Tourmaline-{:%Y-%j}.txt", now);
}
Logging::File.open(File, std::fstream::out);
if (Logging::File.fail()) {
throw std::runtime_error("FAILED! Could not open or create the file: " +
File + "!\n" + strerror(errno));
}
}
void Logging::Log(const std::string &message, const std::string &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);
std::print("\033{} {}\033[0m", loglevelData.second, output);
if (Logging::File.is_open()) {
Logging::File.write(output.c_str(), output.size());
Logging::File.flush(); // Terrible but necessary sadly
}
// Error and Critical
if (severity < Logging::LogLevel::Warning) {
throw std::runtime_error(output);
}
}
}

27
source/Systems/Random.cpp Normal file
View File

@@ -0,0 +1,27 @@
/*
* 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/.
*/
#include "../../headers/Systems/Random.hpp"
#include <bit>
#include <cstdint>
#include <ctime>
using namespace Tourmaline::Systems;
Xoshiro::Xoshiro256PP Random::generator(static_cast<uint64_t>(time(NULL)));
Tourmaline::Type::UUID Random::GenerateUUID() {
uint64_t random_ab = generator(), random_c = generator(), hold = 0;
hold = std::rotr(random_ab, 12);
hold = hold >> 4;
hold = (hold << 4) + 0b0100;
random_ab = std::rotl(hold, 12);
random_c = ((random_c >> 2) << 2) + 2;
return Tourmaline::Type::UUID(random_ab, random_c);
}

69
source/Types/UUID.cpp Normal file
View File

@@ -0,0 +1,69 @@
/*
* 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/.
*/
#include "../../headers/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]);
}
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;
}
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(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
std::from_chars(start, half, data[0], 16);
std::from_chars(half, tail, data[1], 16);
}