diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 6b5cef21..3c7a1847 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -55,14 +55,32 @@ jobs: run: | /opt/homebrew/opt/llvm/bin/llvm-profdata merge -sparse coverage/*.profraw -o coverage/coverage.profdata - SOURCE_FILES=$(find Libraries Sandbox -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) | tr '\n' ' ') - TEST_BINS=$(find Tests -type f -name "Test.*" -perm +111 | tr '\n' ' ') + # Collect all test binaries into an array + TEST_BINS=() + while IFS= read -r bin; do + TEST_BINS+=("$bin") + done < <(find Tests -type f -name "Test.*" -perm +111) - /opt/homebrew/opt/llvm/bin/llvm-cov show $TEST_BINS \ - --show-branches=count \ - -instr-profile=coverage/coverage.profdata \ - --ignore-filename-regex='.*(/usr/|/opt/|/Library/|\.h$)' \ - > coverage/coverage.txt + if [ ${#TEST_BINS[@]} -eq 0 ]; then + echo "No test binaries found!" + exit 1 + fi + + # Build -object flags for every binary after the first + OBJECT_FLAGS=() + for bin in "${TEST_BINS[@]:1}"; do + OBJECT_FLAGS+=(-object "$bin") + done + + /opt/homebrew/opt/llvm/bin/llvm-cov show "${TEST_BINS[0]}" \ + "${OBJECT_FLAGS[@]}" \ + --show-branches=count \ + -instr-profile=coverage/coverage.profdata \ + --ignore-filename-regex='.*(/usr/|/opt/|/Library/)' \ + > coverage/coverage.txt + + # Sanity check — print coverage size so you can see it in logs + wc -l coverage/coverage.txt # ---- MANAGED BUILD ---- - name: Generate managed solution diff --git a/Libraries/LibAsset/Asset.cpp b/Libraries/LibAsset/Asset.cpp deleted file mode 100644 index c2445efe..00000000 --- a/Libraries/LibAsset/Asset.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "Asset.h" - -namespace TerranEngine -{ -} diff --git a/Libraries/LibAsset/Asset.h b/Libraries/LibAsset/Asset.h index 64ef7f00..c8bd3e86 100644 --- a/Libraries/LibAsset/Asset.h +++ b/Libraries/LibAsset/Asset.h @@ -2,38 +2,40 @@ #include "AssetTypes.h" +#include #include +#include +#include #include #include namespace Terran { namespace Asset { -class Asset { +class AssetManager; +class Asset : public Core::RefCounted { public: Asset() = default; - virtual ~Asset() = default; - Asset(AssetHandle const& handle) - : m_handle(handle) + explicit Asset(AssetId const& handle) + : m_id(handle) { } bool is_valid() { - return m_handle.is_valid(); + return m_id.is_valid(); } - AssetHandle const& handle() + AssetId const& id() const { - return m_handle; + return m_id; } virtual AssetTypeId type() const = 0; -protected: - AssetHandle m_handle; - AssetTypeId m_type_id; +private: + AssetId m_id; friend class AssetManager; }; @@ -43,8 +45,8 @@ concept HasStaticType = requires(T) { }; #define TR_DECLARE_ASSET_TYPE(type_name) \ - static AssetTypeId static_type() { return Terran::Core::Hash::fnv1a_64(#type_name); } \ - virtual AssetTypeId type() const override { return static_type(); } + static Terran::Asset::AssetTypeId static_type() { return Terran::Core::Hash::fnv1a_64(#type_name); } \ + virtual Terran::Asset::AssetTypeId type() const override { return static_type(); } class TextAsset final : public Asset { public: diff --git a/Libraries/LibAsset/AssetError.h b/Libraries/LibAsset/AssetError.h new file mode 100644 index 00000000..57d7b4b6 --- /dev/null +++ b/Libraries/LibAsset/AssetError.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace Terran::Asset { + +class AssetError { +public: + virtual ~AssetError() = default; + [[nodiscard]] virtual std::string_view message() const = 0; + [[nodiscard]] virtual std::string_view source() const = 0; +}; + +} diff --git a/Libraries/LibAsset/AssetEvents.h b/Libraries/LibAsset/AssetEvents.h index b9105840..2f799c1f 100644 --- a/Libraries/LibAsset/AssetEvents.h +++ b/Libraries/LibAsset/AssetEvents.h @@ -8,30 +8,30 @@ namespace Terran::Asset { class AssetRemovedEvent { public: - constexpr AssetRemovedEvent(AssetHandle const& handle) noexcept + constexpr AssetRemovedEvent(AssetId const& handle) noexcept : m_handle(handle) { } - [[nodiscard]] constexpr AssetHandle const& handle() const noexcept + [[nodiscard]] constexpr AssetId const& handle() const noexcept { return m_handle; } private: - AssetHandle m_handle; + AssetId m_handle; }; class AssetRenamedEvent { public: - AssetRenamedEvent(AssetHandle const& handle, std::filesystem::path const& new_file_name, std::filesystem::path const& old_file_name) + AssetRenamedEvent(AssetId const& handle, std::filesystem::path const& new_file_name, std::filesystem::path const& old_file_name) : m_handle(handle) , m_new_file_name(new_file_name) , m_old_file_name(old_file_name) { } - [[nodiscard]] constexpr AssetHandle const& handle() const noexcept + [[nodiscard]] constexpr AssetId const& handle() const noexcept { return m_handle; } @@ -40,13 +40,14 @@ class AssetRenamedEvent { { return m_old_file_name; } - - [[nodiscard]] constexpr std::filesystem::path const& new_file_name() const noexcept { + + [[nodiscard]] constexpr std::filesystem::path const& new_file_name() const noexcept + { return m_new_file_name; } private: - AssetHandle m_handle; + AssetId m_handle; std::filesystem::path m_new_file_name; std::filesystem::path m_old_file_name; }; diff --git a/Libraries/LibAsset/AssetHandle.cpp b/Libraries/LibAsset/AssetHandle.cpp new file mode 100644 index 00000000..3e02841b --- /dev/null +++ b/Libraries/LibAsset/AssetHandle.cpp @@ -0,0 +1,16 @@ +#include "AssetHandle.h" +#include "AssetManager.h" + +namespace Terran::Asset { + +AssetHandle::~AssetHandle() +{ + m_asset_manager->enqueue_asset_for_deletion(m_id); +} + +bool AssetHandle::is_valid() const +{ + return m_asset_manager->is_asset_loaded(m_id); +} + +} diff --git a/Libraries/LibAsset/AssetHandle.h b/Libraries/LibAsset/AssetHandle.h new file mode 100644 index 00000000..7f8f6425 --- /dev/null +++ b/Libraries/LibAsset/AssetHandle.h @@ -0,0 +1,50 @@ +#pragma once +#include "AssetTypes.h" + +#include +#include +#include +#include +#include +#include + +namespace Terran::Asset { + +class AssetManager; +class AssetHandle : public Core::RefCounted { +public: + virtual ~AssetHandle() override; + + constexpr AssetId const& id() const + { + return m_id; + } + + bool is_valid() const; + explicit(false) constexpr operator bool() const { + return is_valid(); + } + +private: + // A nifty trick to ensure AssetHandle is only used with RefPtr or WeakPtr + AssetHandle(AssetId const& id, Core::RawPtr asset_manager) + : m_id(id) + , m_asset_manager(asset_manager) + { + } + +private: + AssetId m_id; + Core::RawPtr m_asset_manager; + template + friend class Core::RefPtr; + + template + friend class Core::WeakPtr; +}; + +using StrongAssetHandle = Core::RefPtr; +using WeakAssetHandle = Core::WeakPtr; + +} + diff --git a/Libraries/LibAsset/AssetImporter.h b/Libraries/LibAsset/AssetImporter.h index ff302859..a4ce970d 100644 --- a/Libraries/LibAsset/AssetImporter.h +++ b/Libraries/LibAsset/AssetImporter.h @@ -2,20 +2,26 @@ #include "AssetMetadata.h" #include "Asset.h" +#include "AssetTypes.h" +#include "AssetError.h" -#include #include +#include +#include + #include namespace Terran::Asset { +using AssetLoadResult = Core::Result, Core::Shared>; + class AssetImporter { public: virtual ~AssetImporter() = default; - virtual void load(AssetMetadata const& assetInfo, Terran::Core::Shared& asset) = 0; - virtual bool save(AssetMetadata const& assetInfo, Terran::Core::Shared const& asset) = 0; - virtual bool can_handle(std::filesystem::path const& assetPath) = 0; - virtual AssetTypeId asset_type() = 0; + [[nodiscard]] virtual AssetLoadResult load(AssetMetadata const& assetMetadata) = 0; + virtual bool save(AssetMetadata const& assetMetadata, Core::RefPtr const& asset) = 0; + [[nodiscard]] virtual bool can_handle(std::filesystem::path const& assetPath) = 0; + [[nodiscard]] virtual AssetTypeId asset_type() = 0; }; } diff --git a/Libraries/LibAsset/AssetImporterError.h b/Libraries/LibAsset/AssetImporterError.h new file mode 100644 index 00000000..7454cd91 --- /dev/null +++ b/Libraries/LibAsset/AssetImporterError.h @@ -0,0 +1,41 @@ +#pragma once + +#include "AssetError.h" + +#include +#include + +namespace Terran::Asset { + +class AssetImporterError final : public AssetError { +public: + enum class Code : uint8_t { + ImporterNotFound, + }; + + explicit AssetImporterError(Code code) + : m_code(code) + { + } + + ~AssetImporterError() override = default; + + [[nodiscard]] std::string_view message() const override + { + if (m_code == Code::ImporterNotFound) { + return "No importer registered for asset type"; + } + + return "Unknown error"; + } + + [[nodiscard]] std::string_view source() const override + { + return "Asset"; + } + +private: + Code m_code; +}; + +} diff --git a/Libraries/LibAsset/AssetImporterRegistry.cpp b/Libraries/LibAsset/AssetImporterRegistry.cpp index 0c027797..a0da1cf2 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.cpp +++ b/Libraries/LibAsset/AssetImporterRegistry.cpp @@ -1,36 +1,43 @@ #include "AssetImporterRegistry.h" #include "Asset.h" #include "AssetImporter.h" +#include "AssetImporterError.h" #include "AssetMetadata.h" #include "AssetTypes.h" #include #include +#include +#include #include namespace Terran::Asset { std::unordered_map> AssetImporterRegistry::s_loaders; -void AssetImporterRegistry::load(AssetMetadata const& assetMetadata, Terran::Core::Shared& asset) +AssetLoadResult AssetImporterRegistry::load(AssetMetadata const& assetMetadata) { AssetTypeId const type_id = assetMetadata.Type; if (s_loaders.contains(type_id)) { - s_loaders[type_id]->load(assetMetadata, asset); - return; + return s_loaders[type_id]->load(assetMetadata); } - TR_CORE_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); + TR_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); + return { Core::CreateShared(AssetImporterError::Code::ImporterNotFound) }; } -bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Terran::Core::Shared const& asset) +bool AssetImporterRegistry::save(AssetMetadata const& assetMetadata, Core::RefPtr const& asset) { + if(assetMetadata.Type != asset->type()) { + return false; + } + AssetTypeId const type_id = assetMetadata.Type; if (s_loaders.contains(type_id)) return s_loaders[type_id]->save(assetMetadata, asset); - TR_CORE_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); + TR_ERROR(TR_LOG_ASSET, "Invalid asset type for asset: {0}", assetMetadata.Path); return false; } diff --git a/Libraries/LibAsset/AssetImporterRegistry.h b/Libraries/LibAsset/AssetImporterRegistry.h index 2e3375ed..96df64a1 100644 --- a/Libraries/LibAsset/AssetImporterRegistry.h +++ b/Libraries/LibAsset/AssetImporterRegistry.h @@ -6,6 +6,7 @@ #include "AssetTypes.h" #include +#include #include #include @@ -19,13 +20,13 @@ class AssetImporterRegistry final { requires( std::is_base_of_v, HasStaticType) - static void register_asset(Terran::Core::Shared loader) + static void register_asset(Terran::Core::Shared const& loader) { s_loaders[TAsset::static_type()] = loader; } - static void load(AssetMetadata const& assetMetadata, Terran::Core::Shared& asset); - static bool save(AssetMetadata const& assetMetadata, Terran::Core::Shared const& asset); + static AssetLoadResult load(AssetMetadata const& assetMetadata); + static bool save(AssetMetadata const& assetMetadata, Terran::Core::RefPtr const& asset); static bool exists_for_path(std::filesystem::path const& path) { @@ -47,6 +48,10 @@ class AssetImporterRegistry final { return nullptr; } + static void clear() noexcept { + s_loaders.clear(); + } + private: static std::unordered_map> s_loaders; }; diff --git a/Libraries/LibAsset/AssetManager.cpp b/Libraries/LibAsset/AssetManager.cpp index 95ed6ab5..9a9b07e9 100644 --- a/Libraries/LibAsset/AssetManager.cpp +++ b/Libraries/LibAsset/AssetManager.cpp @@ -1,16 +1,20 @@ #include "AssetManager.h" #include "Asset.h" +#include "AssetEvents.h" +#include "AssetImporter.h" #include "AssetImporterRegistry.h" #include "AssetMetadata.h" #include "AssetMetadataRegistry.h" #include "AssetTypes.h" +#include "AssetHandle.h" -#include "LibCore/Base.h" -#include "LibCore/FileUtils.h" -#include "LibCore/Log.h" - -#include #include +#include +#include +#include +#include +#include + #include #include #include @@ -21,89 +25,126 @@ AssetManager::AssetManager(Core::EventDispatcher& event_dispatcher) : m_event_dispatcher(event_dispatcher) { m_loaded_assets.clear(); - TR_CORE_INFO(TR_LOG_ASSET, "Initialized asset manager"); + TR_INFO(TR_LOG_ASSET, "Initialized asset manager"); } AssetManager::~AssetManager() { m_loaded_assets.clear(); - TR_CORE_INFO(TR_LOG_ASSET, "Shutdown asset manager"); + TR_INFO(TR_LOG_ASSET, "Shutdown asset manager"); } -AssetHandle AssetManager::import_asset(std::filesystem::path const& assetPath) +Core::RefPtr AssetManager::import_asset(std::filesystem::path const& asset_path) const { - AssetHandle assetHandle = AssetMetadataRegistry::asset_handle_from_path(assetPath); + AssetId id = AssetMetadataRegistry::asset_handle_from_path(asset_path); - if (assetHandle) - return assetHandle; + if (id) { + return create_asset_handle(id); + } - assetHandle = AssetHandle(); + id = AssetId(); - if (assetPath.empty()) - return AssetHandle::invalid(); + if (asset_path.empty()) { + TR_ERROR(TR_LOG_ASSET, "Failed to import asset, due to empty asset path!"); + return nullptr; + } - auto const assetLoader = AssetImporterRegistry::find_for_path(assetPath); - if (assetLoader) - return AssetHandle::invalid(); + auto const asset_loader = AssetImporterRegistry::find_for_path(asset_path); + if (!asset_loader) { + TR_ERROR(TR_LOG_ASSET, "No registered loaders for asset with path {}", asset_path); + return nullptr; + } - AssetMetadata assetMetadata; - assetMetadata.Path = assetPath; - assetMetadata.Type = assetLoader->asset_type(); - assetMetadata.Handle = assetHandle; + AssetMetadata asset_metadata; + asset_metadata.Path = asset_path; + asset_metadata.Type = asset_loader->asset_type(); + asset_metadata.AssetId = id; - AssetMetadataRegistry::add_asset_metadata(assetMetadata); + AssetMetadataRegistry::add_asset_metadata(asset_metadata); - return assetHandle; + return create_asset_handle(id); } -void AssetManager::reload_asset_by_handle(AssetHandle const& handle) +void AssetManager::reload_asset_by_id(AssetId const& asset_id) { - AssetMetadata const& info = AssetMetadataRegistry::asset_metadata_by_handle(handle); - if (!m_loaded_assets.contains(handle)) { - TR_CORE_WARN(TR_LOG_ASSET, "Trying to reload an asset that was never loaded"); - Terran::Core::Shared asset; - AssetImporterRegistry::load(info, asset); + AssetMetadata const& metadata = AssetMetadataRegistry::asset_metadata_by_handle(asset_id); + + if (!m_loaded_assets.contains(asset_id)) + TR_WARN(TR_LOG_ASSET, "Trying to reload an asset that was never loaded"); + + AssetLoadResult asset_result = AssetImporterRegistry::load(metadata); + if (!asset_result) { + TR_ERROR(TR_LOG_ASSET, "Failed to load asset with Id: {} and Path: {}", asset_id, metadata.Path); return; } - Terran::Core::Shared& asset = m_loaded_assets.at(handle); - AssetImporterRegistry::load(info, asset); + + m_loaded_assets[asset_id] = asset_result.value(); } -void AssetManager::on_filesystem_changed(std::vector const& fileSystemEvents) +Core::Result AssetManager::remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately, RemoveAssetMetadata remove_metadata) +{ + if (!m_loaded_assets.contains(handle)) { + TR_ERROR(TR_LOG_ASSET, "Asset with id {} wasn't found!", handle); + return { AssetRemoveError::AssetNotFound }; + } + + if (remove_metadata == RemoveAssetMetadata::Yes && !AssetMetadataRegistry::contains(handle)) { + TR_ERROR(TR_LOG_ASSET, "Asset metadata with id {} wasn't found!", handle); + return { AssetRemoveError::MetadatNotFound }; + } + + if (remove_immediately == RemoveAssetImmediately::Yes) { + m_loaded_assets.erase(handle); + } else { + m_free_queue.emplace_back(handle); + } + + if (remove_metadata == RemoveAssetMetadata::Yes) { + AssetMetadataRegistry::erase(handle); + } + + return {}; +} + +void AssetManager::on_filesystem_changed(std::vector const& file_system_events) { if (m_asset_change_callback) - m_asset_change_callback(fileSystemEvents); + m_asset_change_callback(file_system_events); - for (auto const& e : fileSystemEvents) { - if (std::filesystem::is_directory(e.FileName)) + for (auto const& event : file_system_events) { + if (std::filesystem::is_directory(event.FileName)) continue; - switch (e.Action) { - case Terran::Core::FileAction::Removed: { - on_asset_removed(AssetMetadataRegistry::asset_handle_from_path(e.FileName)); + switch (event.Action) { + case Core::FileAction::Removed: { + on_asset_removed(AssetMetadataRegistry::asset_handle_from_path(event.FileName)); break; } - case Terran::Core::FileAction::Renamed: { - bool was_asset = AssetImporterRegistry::exists_for_path(e.OldFileName); - bool is_asset = AssetImporterRegistry::exists_for_path(e.FileName); + case Core::FileAction::Renamed: { + bool was_asset = AssetImporterRegistry::exists_for_path(event.OldFileName); + bool is_asset = AssetImporterRegistry::exists_for_path(event.FileName); if (!was_asset && is_asset) { - import_asset(e.FileName); + import_asset(event.FileName); } else if (was_asset && !is_asset) { on_asset_removed( - AssetMetadataRegistry::asset_handle_from_path(e.OldFileName)); + AssetMetadataRegistry::asset_handle_from_path(event.OldFileName)); } else { on_asset_renamed( - AssetMetadataRegistry::asset_handle_from_path(e.OldFileName), e.FileName); + AssetMetadataRegistry::asset_handle_from_path(event.OldFileName), event.FileName); } break; } - case Terran::Core::FileAction::Modified: { - AssetMetadata info = AssetMetadataRegistry::asset_metadata_by_path(e.FileName); + case Core::FileAction::Modified: { + AssetMetadata metadata = AssetMetadataRegistry::asset_metadata_by_path(event.FileName); - if (info) - reload_asset_by_handle(info.Handle); + if (!metadata) { + TR_WARN(TR_LOG_ASSET, "Asset metadata wasn't found for asset with path: {}", event.FileName); + break; + } + + reload_asset_by_id(metadata.AssetId); break; } @@ -111,28 +152,43 @@ void AssetManager::on_filesystem_changed(std::vector #include #include #include #include +#include +#include +#include +#include +#include #include +#include #include +#include #include #include #include namespace Terran::Asset { +enum class RemoveAssetMetadata : uint8_t { + No = 0, + Yes = 1 +}; + +enum class RemoveAssetImmediately : uint8_t { + No = 0, + Yes = 1, +}; + +enum class AssetRemoveError : uint8_t { + AssetNotFound, + MetadatNotFound +}; + class AssetManager final { - using AssetChangeCallbackFn = std::function const&)>; + using AssetChangeCallbackFn = std::function const&)>; + using asset_container_type = std::unordered_map>; + using free_queue_type = std::deque; public: AssetManager(Core::EventDispatcher& event_dispatcher); ~AssetManager(); - std::filesystem::path filesystem_path(std::filesystem::path const& path); + StrongAssetHandle import_asset(std::filesystem::path const& asset_path) const; - AssetHandle import_asset(std::filesystem::path const& assetPath); - void reload_asset_by_handle(AssetHandle const& handle); + void reload_asset_by_id(AssetId const& asset_id); + void reload_asset_by_handle(StrongAssetHandle const& asset_handle) + { + reload_asset_by_id(asset_handle->id()); + } - void SetAssetChangedCallback(AssetChangeCallbackFn const& callback) { m_asset_change_callback = callback; } + void SetAssetChangedCallback(AssetChangeCallbackFn const& callback) { m_asset_change_callback = callback; } template requires(std::is_base_of_v) - Terran::Core::Shared asset_by_handle(AssetHandle const& assetHandle) + Core::RefPtr asset_by_handle(StrongAssetHandle const& asset_handle) { - if (m_loaded_assets.contains(assetHandle)) - return Terran::Core::DynamicCast(m_loaded_assets.at(assetHandle)); + if (m_loaded_assets.contains(asset_handle->id())) + return asset_by_id(asset_handle->id()); - AssetMetadata& info = AssetMetadataRegistry::asset_metadata_by_handle__internal(assetHandle); + AssetMetadata& info = AssetMetadataRegistry::asset_metadata_by_handle__internal(asset_handle->id()); if (!info) return nullptr; - // NOTE: poc code - Terran::Core::Shared asset = nullptr; - AssetImporterRegistry::load(info, asset); + AssetLoadResult assetResult = AssetImporterRegistry::load(info); - if (!asset) { - TR_CORE_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", info.Path); + if (!assetResult) { + TR_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", info.Path); return nullptr; } - asset->m_handle = assetHandle; - m_loaded_assets[assetHandle] = asset; - return Terran::Core::DynamicCast(m_loaded_assets[assetHandle]); + Core::RefPtr const& asset = assetResult.value(); + asset->m_id = asset_handle->id(); + m_loaded_assets[asset_handle->id()] = asset; + return Core::dynamic_pointer_cast(asset); } template requires(std::is_base_of_v) - Terran::Core::Shared asset_by_metadata(AssetMetadata const& assetMetadata) + Core::RefPtr asset_by_metadata(AssetMetadata const& asset_metadata) { - if (m_loaded_assets.contains(assetMetadata.Handle)) - return Terran::Core::DynamicCast(m_loaded_assets.at(assetMetadata.Handle)); + if (m_loaded_assets.contains(asset_metadata.AssetId)) + return asset_by_id(asset_metadata.AssetId); - // NOTE: poc code - Terran::Core::Shared asset = nullptr; - AssetImporterRegistry::load(assetMetadata, asset); + AssetLoadResult asset_result = AssetImporterRegistry::load(asset_metadata); - if (!asset) { - TR_CORE_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", assetMetadata.Path); + if (!asset_result) { + TR_ERROR(TR_LOG_ASSET, "Failed to load asset with path: {0}", asset_metadata.Path); return nullptr; } - asset->m_handle = assetMetadata.Handle; - m_loaded_assets[assetMetadata.Handle] = asset; - return Terran::Core::DynamicCast(m_loaded_assets[assetMetadata.Handle]); + Core::RefPtr const& asset = asset_result.value(); + asset->m_id = asset_metadata.AssetId; + m_loaded_assets[asset_metadata.AssetId] = asset; + return Core::dynamic_pointer_cast(asset); } - template + template requires( - std::is_base_of_v, - HasStaticType) - Terran::Core::Shared create_asset(std::filesystem::path const& filePath) + std::is_base_of_v && HasStaticType) + void save_asset(Core::RefPtr asset) { - AssetMetadata metadata; - metadata.Handle = AssetHandle(); - metadata.Path = filePath; - metadata.Type = TAsset::static_type(); - - int currentFileNumber = 2; - while (file_exists(metadata.Path)) { - metadata.Path = filePath.parent_path() / filePath.stem(); - - metadata.Path = metadata.Path.string() + " (" + std::to_string(currentFileNumber) + ")" + filePath.extension().string(); - - currentFileNumber++; + if (!AssetMetadataRegistry::contains(asset->id())) { + TR_ERROR(TR_LOG_ASSET, "Failed to save asset {} as there is no metadata associated with it", asset->id()); + return; + } + AssetMetadata& metadata = AssetMetadataRegistry::asset_metadata_by_handle__internal(asset->id()); + + int file_occurrence_count = 2; + while (std::filesystem::exists(metadata.Path)) { + // Format: parent_directory/file_name (occurrence_count).extension + std::filesystem::path file_path_without_extension = metadata.Path.parent_path() / metadata.Path.stem(); + std::filesystem::path file_extension = metadata.Path.extension(); + metadata.Path = std::format("{} ({}){}", + file_path_without_extension.string(), + file_occurrence_count, + file_extension.string()); + + file_occurrence_count++; } - AssetMetadataRegistry::add_asset_metadata(metadata); - - // TODO: add parameter options - Terran::Core::Shared asset = Terran::Core::CreateShared(); - - m_loaded_assets[metadata.Handle] = asset; AssetImporterRegistry::save(metadata, asset); + } - return Terran::Core::DynamicCast(m_loaded_assets[metadata.Handle]); + // NOTE: maybe we should take in a parameter that signifies + // whether to create metadata for this asset + // something like create_memory_asset(CreateAssetMetadata::Yes) + StrongAssetHandle add_asset(Core::RefPtr const& asset) + { + m_loaded_assets[asset->id()] = asset; + return create_asset_handle(asset->id()); } - template + template requires(std::is_base_of_v) - Terran::Core::Shared create_memory_asset() + Core::RefPtr create_asset(TArgs&&... args) + { + Core::RefPtr asset = Core::RefPtr::create(std::forward(args)...); + return Core::dynamic_pointer_cast(asset); + } + + template + requires( + std::is_base_of_v && HasStaticType) + AssetMetadata create_asset_metadata(AssetId const& asset_id, std::filesystem::path const& file_path) const { - Terran::Core::Shared asset = Terran::Core::CreateShared(); - m_loaded_assets[asset->m_handle] = asset; + AssetMetadata metadata; + metadata.AssetId = asset_id; + metadata.Path = file_path; + metadata.Type = TAsset::static_type(); + return metadata; + } + + Core::Result remove_asset(Core::UUID const& handle, RemoveAssetImmediately remove_immediately = RemoveAssetImmediately::Yes, RemoveAssetMetadata remove_metadata = RemoveAssetMetadata::Yes); - return Terran::Core::DynamicCast(m_loaded_assets[asset->m_handle]); + constexpr bool is_asset_loaded(AssetId const& id) const + { + return m_loaded_assets.contains(id); } - static bool file_exists(std::filesystem::path const& path); + void purge_stale(); private: - void on_filesystem_changed(std::vector const& fileSystemEvents); - void on_asset_removed(AssetHandle const& handle); - void on_asset_renamed(AssetHandle const& handle, std::filesystem::path const& newFileName); - std::filesystem::path relative_path(std::filesystem::path const& path); + void on_filesystem_changed(std::vector const& file_system_events); + void on_asset_removed(AssetId const& handle); + void on_asset_renamed(AssetId const& handle, std::filesystem::path const& new_file_name); + void enqueue_asset_for_deletion(AssetId const& handle) const; + + template + requires(std::is_base_of_v) + inline Core::RefPtr asset_by_id(AssetId const& id) + { + return Core::dynamic_pointer_cast(m_loaded_assets.at(id)); + } + + inline StrongAssetHandle create_asset_handle(AssetId const& asset_id) const + { + return StrongAssetHandle::create(asset_id, this); + } private: - std::unordered_map> m_loaded_assets; + asset_container_type m_loaded_assets; AssetChangeCallbackFn m_asset_change_callback; Core::EventDispatcher& m_event_dispatcher; + // NOTE: this needs to be a multithreaded queue, either with or without a lock!!! + // This works for now because the engine is not multithreaded + // Will cause a bunch of problems if not changed and the engine goes multithreaded!!! + mutable free_queue_type m_free_queue; + friend class Asset; + friend class AssetHandle; }; } diff --git a/Libraries/LibAsset/AssetMetadata.h b/Libraries/LibAsset/AssetMetadata.h index aac10348..8f31d4d1 100644 --- a/Libraries/LibAsset/AssetMetadata.h +++ b/Libraries/LibAsset/AssetMetadata.h @@ -23,12 +23,12 @@ struct AssetMetadata final { operator bool() const { - return Path != "" && Handle.is_valid(); + return Path != "" && AssetId.is_valid(); } std::filesystem::path Path = ""; AssetTypeId Type; - AssetHandle Handle = AssetHandle::invalid(); + AssetId AssetId = AssetId::invalid(); }; } diff --git a/Libraries/LibAsset/AssetMetadataRegistry.cpp b/Libraries/LibAsset/AssetMetadataRegistry.cpp index ae75e948..96358c05 100644 --- a/Libraries/LibAsset/AssetMetadataRegistry.cpp +++ b/Libraries/LibAsset/AssetMetadataRegistry.cpp @@ -14,14 +14,15 @@ namespace Terran { namespace Asset { -std::map AssetMetadataRegistry::s_asset_metadata; +std::map AssetMetadataRegistry::s_asset_metadata; static AssetMetadata s_invalid_asset_info; -AssetMetadata& AssetMetadataRegistry::asset_metadata_by_handle__internal(AssetHandle const& handle) +AssetMetadata& AssetMetadataRegistry::asset_metadata_by_handle__internal(AssetId const& handle) { if (s_asset_metadata.contains(handle)) return s_asset_metadata.at(handle); + TR_ERROR(TR_LOG_ASSET, "Failed to find asset metadata by handle {}", handle); return s_invalid_asset_info; } @@ -30,11 +31,12 @@ AssetMetadata& AssetMetadataRegistry::asset_metadata_by_handle__internal(AssetHa // return std::filesystem::relative(path, Project::GetAssetPath()); // } -AssetMetadata const& AssetMetadataRegistry::asset_metadata_by_handle(AssetHandle const& handle) +AssetMetadata const& AssetMetadataRegistry::asset_metadata_by_handle(AssetId const& handle) { if (s_asset_metadata.contains(handle)) return s_asset_metadata.at(handle); + TR_ERROR(TR_LOG_ASSET, "Failed to find asset metadata by handle {}", handle); return s_invalid_asset_info; } @@ -45,17 +47,19 @@ AssetMetadata const& AssetMetadataRegistry::asset_metadata_by_path(std::filesyst return asset_metadata; } + TR_ERROR(TR_LOG_ASSET, "Failed to find asset metadata from path {}", assetPath); return s_invalid_asset_info; } -AssetHandle AssetMetadataRegistry::asset_handle_from_path(std::filesystem::path const& assetPath) +AssetId AssetMetadataRegistry::asset_handle_from_path(std::filesystem::path const& assetPath) { for (auto const& [handle, asset_metadata] : s_asset_metadata) { if (asset_metadata.Path == assetPath) return handle; } - return AssetHandle::invalid(); + TR_ERROR(TR_LOG_ASSET, "Failed to find asset handle from path {}", assetPath); + return AssetId::invalid(); } void AssetMetadataRegistry::serialize_asset_metadata(YAML::Emitter& out) @@ -65,7 +69,7 @@ void AssetMetadataRegistry::serialize_asset_metadata(YAML::Emitter& out) for (auto const& [assetID, assetInfo] : s_asset_metadata) { out << YAML::BeginMap; - out << YAML::Key << "Asset" << YAML::Value << std::to_string(assetInfo.Handle); + out << YAML::Key << "Asset" << YAML::Value << std::to_string(assetInfo.AssetId); out << YAML::Key << "Type" << YAML::Value << assetInfo.Type; out << YAML::Key << "Path" << YAML::Value << assetInfo.Path.string(); out << YAML::EndMap; @@ -84,11 +88,11 @@ void AssetMetadataRegistry::deserialize_asset_metadata(YAML::Node const& node) for (auto const& assetInfo : assetInfos) { AssetMetadata info; - AssetHandle assetHandle = AssetHandle::from_string(assetInfo["Asset"].as()); + AssetId assetHandle = AssetId::from_string(assetInfo["Asset"].as()); info.Type = assetInfo["Type"].as(); info.Path = assetInfo["Path"].as(); - info.Handle = assetHandle; + info.AssetId = assetHandle; if (info.Path.empty()) continue; @@ -97,7 +101,7 @@ void AssetMetadataRegistry::deserialize_asset_metadata(YAML::Node const& node) } } } catch (YAML::BadSubscript const& e) { - TR_CORE_ERROR(TR_LOG_ASSET, e.what()); + TR_ERROR(TR_LOG_ASSET, e.what()); return; } } @@ -117,20 +121,24 @@ void AssetMetadataRegistry::load_asset_metadata_from_file(std::filesystem::path try { node = YAML::LoadFile(path.string()); } catch (YAML::Exception const& e) { - TR_CORE_ERROR(TR_LOG_ASSET, e.what()); + TR_ERROR(TR_LOG_ASSET, e.what()); return; } deserialize_asset_metadata(node); } -void AssetMetadataRegistry::erase(AssetHandle const& handle) { +void AssetMetadataRegistry::erase(AssetId const& handle) { s_asset_metadata.erase(handle); } -bool AssetMetadataRegistry::contains(AssetHandle const& handle) { +bool AssetMetadataRegistry::contains(AssetId const& handle) { return s_asset_metadata.contains(handle); } +void AssetMetadataRegistry::add_asset_metadata(const AssetMetadata &metadata) { + s_asset_metadata.emplace(metadata.AssetId, metadata); +} + } } diff --git a/Libraries/LibAsset/AssetMetadataRegistry.h b/Libraries/LibAsset/AssetMetadataRegistry.h index 5003eb34..97da3e83 100644 --- a/Libraries/LibAsset/AssetMetadataRegistry.h +++ b/Libraries/LibAsset/AssetMetadataRegistry.h @@ -12,14 +12,14 @@ namespace Terran { namespace Asset { class AssetMetadataRegistry final { - using AssetMetadataMap = std::map; + using AssetMetadataMap = std::map; public: - static AssetMetadata const& asset_metadata_by_handle(AssetHandle const& assetHandle); + static AssetMetadata const& asset_metadata_by_handle(AssetId const& assetHandle); static AssetMetadata const& asset_metadata_by_path(std::filesystem::path const& assetPath); - static AssetHandle asset_handle_from_path(std::filesystem::path const& assetPath); + static AssetId asset_handle_from_path(std::filesystem::path const& assetPath); static void add_asset_metadata(AssetMetadata const& metadata); @@ -36,9 +36,9 @@ class AssetMetadataRegistry final { static void deserialize_asset_metadata(YAML::Node const& node); - static void erase(AssetHandle const& handle); + static void erase(AssetId const& handle); - static bool contains(AssetHandle const& handle); + static bool contains(AssetId const& handle); static void clear() { @@ -46,7 +46,7 @@ class AssetMetadataRegistry final { } private: - static AssetMetadata& asset_metadata_by_handle__internal(AssetHandle const& handle); + static AssetMetadata& asset_metadata_by_handle__internal(AssetId const& handle); static AssetMetadataMap s_asset_metadata; diff --git a/Libraries/LibAsset/AssetSystem.h b/Libraries/LibAsset/AssetSystem.h index 42b79037..453063e7 100644 --- a/Libraries/LibAsset/AssetSystem.h +++ b/Libraries/LibAsset/AssetSystem.h @@ -1,10 +1,12 @@ #pragma once #include "AssetManager.h" +#include "AssetTypes.h" #include #include #include +#include namespace Terran::Asset { @@ -14,6 +16,7 @@ class AssetSystem final : public Core::Layer { AssetSystem(Core::EventDispatcher& dispatcher) : Core::Layer("Asset", dispatcher) { + Core::Log::add_logger(TR_LOG_ASSET); m_asset_manager = Core::CreateShared(dispatcher); } diff --git a/Libraries/LibAsset/AssetTypes.h b/Libraries/LibAsset/AssetTypes.h index fa7e358e..29fe1b51 100644 --- a/Libraries/LibAsset/AssetTypes.h +++ b/Libraries/LibAsset/AssetTypes.h @@ -7,9 +7,10 @@ namespace Terran { namespace Asset { -using AssetHandle = Terran::Core::UUID; +using AssetId = Terran::Core::UUID; + using AssetTypeId = uint64_t; -#define TR_LOG_ASSET "Asset" +constexpr char const* const TR_LOG_ASSET = "Asset"; } } diff --git a/Libraries/LibCore/Assert.h b/Libraries/LibCore/Assert.h index e9148d57..0001536e 100644 --- a/Libraries/LibCore/Assert.h +++ b/Libraries/LibCore/Assert.h @@ -10,10 +10,10 @@ #endif #if defined(TR_DEBUG) -# define TR_ASSERT(condition, ...) \ - if (!(condition)) { \ - TR_CORE_ERROR(TR_LOG_CORE, __VA_ARGS__); \ - TR_DEBUGBREAK(); \ +# define TR_ASSERT(condition, ...) \ + if (!(condition)) { \ + TR_ERROR(TR_LOG_CORE, __VA_ARGS__); \ + TR_DEBUGBREAK(); \ } #elif defined(TR_RELEASE) # define TR_ASSERT(condition, ...) diff --git a/Libraries/LibCore/DefaultDelete.h b/Libraries/LibCore/DefaultDelete.h index a34028b0..89773d70 100644 --- a/Libraries/LibCore/DefaultDelete.h +++ b/Libraries/LibCore/DefaultDelete.h @@ -5,6 +5,7 @@ */ #pragma once +#include namespace Terran { namespace Core { @@ -17,6 +18,12 @@ namespace Core { template struct DefaultDelete { constexpr DefaultDelete() noexcept = default; + + constexpr void operator()(T const* value) noexcept + { + delete value; + } + constexpr void operator()(T* value) noexcept { delete value; @@ -39,5 +46,12 @@ struct DefaultDelete { } }; +template +concept IsDefaultDelete = requires(T t, T* ptr, T const* const_ptr) { + { t(ptr) } -> std::same_as ; + { t(const_ptr) } -> std::same_as ; + +}; + } } diff --git a/Libraries/LibCore/LayerStack.h b/Libraries/LibCore/LayerStack.h index 14a5993c..5dd04824 100644 --- a/Libraries/LibCore/LayerStack.h +++ b/Libraries/LibCore/LayerStack.h @@ -45,11 +45,11 @@ class LayerStack final { * * @param args The arguments passed to the constructor of the layer */ - template + template requires(std::is_base_of_v) - Result push(Args... args) + Result push(TArgs&&... args) { - Unique layer = Unique::create(*m_dispatcher, std::forward(args)...); + Unique layer = Unique::create(*m_dispatcher, std::forward(args)...); auto result = layer->on_attach(); if (!result.is_ok()) diff --git a/Libraries/LibCore/Log.cpp b/Libraries/LibCore/Log.cpp index 9ab8933e..24f218e2 100644 --- a/Libraries/LibCore/Log.cpp +++ b/Libraries/LibCore/Log.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace Terran::Core { @@ -41,7 +42,7 @@ static spdlog::level::level_enum GetLogLevel(std::string const& logLevel) if (logLevel == "off") return spdlog::level::level_enum::off; - TR_CORE_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); + TR_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); return spdlog::level::level_enum::trace; } @@ -64,7 +65,7 @@ static std::string LogLevelToString(spdlog::level::level_enum logLevel) return "trace"; } - TR_CORE_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); + TR_WARN(TR_LOG_CORE, "Invalid log level, defaulting to trace"); return "trace"; } @@ -75,7 +76,7 @@ static void load_log_settings() try { node = YAML::LoadFile(s_SettingsPath.string()); } catch (YAML::Exception const& e) { - TR_CORE_ERROR(TR_LOG_CORE, e.what()); + TR_ERROR(TR_LOG_CORE, e.what()); return; } @@ -96,7 +97,7 @@ static void load_log_settings() index++; } } catch (YAML::BadSubscript const& e) { - TR_CORE_ERROR(TR_LOG_CORE, e.what()); + TR_ERROR(TR_LOG_CORE, e.what()); return; } } diff --git a/Libraries/LibCore/Log.h b/Libraries/LibCore/Log.h index 89fd7566..67de0670 100644 --- a/Libraries/LibCore/Log.h +++ b/Libraries/LibCore/Log.h @@ -47,37 +47,37 @@ class Log final { static void add_logger(std::string const& logger_name); }; -#define TR_CORE_TRACE(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->trace(__VA_ARGS__) +#define TR_TRACE(LoggerName, ...) \ + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->trace(__VA_ARGS__) -#define TR_CORE_INFO(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->info(__VA_ARGS__) +#define TR_INFO(LoggerName, ...) \ + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->info(__VA_ARGS__) -#define TR_CORE_WARN(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->warn(__VA_ARGS__) +#define TR_WARN(LoggerName, ...) \ + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->warn(__VA_ARGS__) -#define TR_CORE_ERROR(LoggerName, ...) \ - if (Terran::Core::Log::contains(LoggerName)) \ - Terran::Core::Log::logger(LoggerName)->error(__VA_ARGS__) +#define TR_ERROR(LoggerName, ...) \ + if (::Terran::Core::Log::contains(LoggerName)) \ + ::Terran::Core::Log::logger(LoggerName)->error(__VA_ARGS__) #define TR_CLIENT_TRACE(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->trace(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->trace(__VA_ARGS__) #define TR_CLIENT_INFO(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->info(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->info(__VA_ARGS__) #define TR_CLIENT_WARN(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->warn(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->warn(__VA_ARGS__) #define TR_CLIENT_ERROR(...) \ - if (Terran::Core::Log::contains(TR_LOG_CLIENT)) \ - Terran::Core::Log::logger(TR_LOG_CLIENT)->error(__VA_ARGS__) + if (::Terran::Core::Log::contains(TR_LOG_CLIENT)) \ + ::Terran::Core::Log::logger(TR_LOG_CLIENT)->error(__VA_ARGS__) } diff --git a/Libraries/LibCore/Math.cpp b/Libraries/LibCore/Math.cpp new file mode 100644 index 00000000..d562f847 --- /dev/null +++ b/Libraries/LibCore/Math.cpp @@ -0,0 +1,79 @@ +#include "Math.h" + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include +#include + +#include + +namespace Terran::Core { + +bool Math::decompose_transform_matrix(glm::mat4 const& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale) +{ + glm::mat4 LocalMatrix(modelMatrix); + + // Normalize the matrix. + if (glm::epsilonEqual(LocalMatrix[3][3], static_cast(0), glm::epsilon())) + return false; + + for (glm::length_t i = 0; i < 4; ++i) + for (glm::length_t j = 0; j < 4; ++j) + LocalMatrix[i][j] /= LocalMatrix[3][3]; + + if ( + glm::epsilonNotEqual(LocalMatrix[0][3], static_cast(0), glm::epsilon()) || glm::epsilonNotEqual(LocalMatrix[1][3], static_cast(0), glm::epsilon()) || glm::epsilonNotEqual(LocalMatrix[2][3], static_cast(0), glm::epsilon())) { + LocalMatrix[0][3] = LocalMatrix[1][3] = LocalMatrix[2][3] = static_cast(0); + LocalMatrix[3][3] = static_cast(1); + } + + translation = glm::vec3(LocalMatrix[3]); + LocalMatrix[3] = glm::vec4(0, 0, 0, LocalMatrix[3].w); + + std::array Row; + glm::vec3 Pdum3; + + for (glm::length_t i = 0; i < 3; ++i) + for (glm::length_t j = 0; j < 3; ++j) + Row[i][j] = LocalMatrix[i][j]; + + // get X scale factor and normalize first row. + scale.x = glm::length(Row[0]); + + Row[0] = glm::detail::scale(Row[0], static_cast(1)); + + // get Y scale and normalize 2nd row. + scale.y = glm::length(Row[1]); + Row[1] = glm::detail::scale(Row[1], static_cast(1)); + + // get Z scale and normalize 3rd row. + scale.z = glm::length(Row[2]); + Row[2] = glm::detail::scale(Row[2], static_cast(1)); + + Pdum3 = cross(Row[1], Row[2]); + if (dot(Row[0], Pdum3) < 0) { + for (glm::length_t i = 0; i < 3; i++) { + scale[i] *= static_cast(-1); + Row[i] *= static_cast(-1); + } + } + + // get the rotations out. + rotation.y = asin(-Row[0][2]); + if (cos(rotation.y) != 0) { + rotation.x = atan2(Row[1][2], Row[2][2]); + rotation.z = atan2(Row[0][1], Row[0][0]); + } else { + rotation.x = atan2(-Row[2][0], Row[1][1]); + rotation.z = 0; + } + + return true; +} + +glm::mat4 Math::compose_transform_matrix(glm::vec3 const& translation, glm::vec3 const& rotation, glm::vec3 const& scale) +{ + return glm::translate(glm::mat4(1.0f), translation) * glm::toMat4(glm::quat(rotation)) * glm::scale(glm::mat4(1.0f), scale); +} + +} diff --git a/Libraries/LibCore/Math.h b/Libraries/LibCore/Math.h new file mode 100644 index 00000000..57e1610b --- /dev/null +++ b/Libraries/LibCore/Math.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace Terran::Core::Math { + +bool decompose_transform_matrix(glm::mat4 const& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale); +glm::mat4 compose_transform_matrix(glm::vec3 const& translation, glm::vec3 const& rotation, glm::vec3 const& scale); + +} + diff --git a/Libraries/LibCore/RefCounted.h b/Libraries/LibCore/RefCounted.h new file mode 100644 index 00000000..26ad4e96 --- /dev/null +++ b/Libraries/LibCore/RefCounted.h @@ -0,0 +1,91 @@ +#pragma once + +#include "DefaultDelete.h" + +#include +#include +#include + +namespace Terran::Core { + +// NOTE: meant to be used ONLY internally by RefCounted, RefPtr and WeakPtr +// meant to be allocated on the heap +class WeakData { + +public: + void increment_weak_count() const + { + m_weak_count++; + } + + void decrement_weak_count() const + { + m_weak_count--; + if (weak_count() == 0) { + DefaultDelete deleter; + deleter(this); + } + } + + size_t weak_count() const + { + return m_weak_count.load(); + } + + bool is_alive() const noexcept + { + return m_is_alive.load(); + } + + void mark_dead() noexcept + { + m_is_alive.store(false); + } + +private: + mutable std::atomic_size_t m_weak_count = 0; + mutable std::atomic_bool m_is_alive; +}; + +class RefCounted { +public: + virtual ~RefCounted() + { + if (m_weak_data) { + m_weak_data->mark_dead(); + m_weak_data->decrement_weak_count(); + } + } + + void increment_count() const + { + m_reference_count++; + } + + void decrement_count() const + { + m_reference_count--; + } + + size_t ref_count() const + { + return m_reference_count.load(); + } + + WeakData* acquire_weak_data() const + { + if (!m_weak_data) { + m_weak_data = new WeakData(); + } + return m_weak_data; + } + +private: + mutable std::atomic_size_t m_reference_count = 0; + mutable WeakData* m_weak_data = nullptr; +}; + +template +concept IsRefCounted = std::is_base_of_v; + +} diff --git a/Libraries/LibCore/RefPtr.h b/Libraries/LibCore/RefPtr.h new file mode 100644 index 00000000..19ba7a34 --- /dev/null +++ b/Libraries/LibCore/RefPtr.h @@ -0,0 +1,224 @@ +#pragma once + +#include "DefaultDelete.h" +#include "RefCounted.h" +#include "Unique.h" + +#include +#include +#include + +namespace Terran::Core { + + +template +class RefPtr final { + using value_type = TValue; + +public: + constexpr RefPtr() noexcept = default; + explicit(false) constexpr RefPtr(std::nullptr_t) noexcept + { + } + + explicit(false) constexpr RefPtr(TValue* data) noexcept + : m_data(data) + { + increment_ref(); + } + + template + requires(std::is_convertible_v) + explicit(false) constexpr RefPtr(TYValue* data) noexcept + : m_data(data) + { + increment_ref(); + } + + template + requires(std::is_convertible_v) + explicit(false) constexpr RefPtr(RefPtr const& other) noexcept + : m_data(other.m_data) + { + increment_ref(); + } + + template + requires(std::is_convertible_v) + explicit(false) constexpr RefPtr(RefPtr&& other) noexcept + : m_data(other.release()) + { + } + + explicit(false) constexpr RefPtr(RefPtr const& other) noexcept + : m_data(other.m_data) + { + increment_ref(); + } + + explicit(false) constexpr RefPtr(RefPtr&& other) noexcept + : m_data(other.release()) + { + } + + template + RefPtr(Unique const& other) = delete; + + constexpr ~RefPtr() + { + reset(); + } + + constexpr void reset() noexcept + { + decrement_ref(); + } + + [[nodiscard]] constexpr value_type* release() noexcept + { + return std::exchange(m_data, nullptr); + } + + [[nodiscard]] constexpr value_type* data() const noexcept + { + return m_data; + } + + void swap(RefPtr& other) noexcept + { + std::swap(m_data, other.m_data); + } + + template + requires(std::is_convertible_v) + void swap(RefPtr& other) noexcept + { + std::swap(m_data, other.m_data); + } + + constexpr bool operator==(RefPtr const& other) const noexcept + { + return m_data == other.m_data; + } + + constexpr value_type* operator->() const noexcept + { + return m_data; + } + + constexpr RefPtr& operator=(std::nullptr_t) noexcept + { + decrement_ref(); + m_data = nullptr; + return *this; + } + + constexpr RefPtr& operator=(RefPtr const& other) noexcept + { + if (this == &other) + return *this; + + other.increment_ref(); + decrement_ref(); + m_data = other.m_data; + return *this; + } + + template + requires(std::is_convertible_v) + constexpr RefPtr& operator=(RefPtr const& other) noexcept + { + if (this == &other) + return *this; + + other.increment_ref(); + decrement_ref(); + m_data = other.m_data; + return *this; + } + + constexpr RefPtr& operator=(RefPtr&& other) noexcept + { + // NOTE: this cleans up memory because temp gets destroyed at the end of the scope + RefPtr temp { std::move(other) }; + swap(temp); + return *this; + } + template + requires(std::is_convertible_v) + constexpr RefPtr& operator=(RefPtr&& other) noexcept + { + RefPtr temp { std::move(other) }; + swap(temp); + return *this; + } + + constexpr RefPtr& operator=(TValue* data) noexcept + { + m_data = data; + increment_ref(); + return *this; + } + + template + RefPtr& operator=(Unique const& other) = delete; + + explicit(false) constexpr operator bool() const noexcept + { + return !is_null(); + } + + constexpr bool is_null() const noexcept + { + return !m_data; + } + + template + static constexpr RefPtr create(TArgs&&... args) + { + return RefPtr(new TValue(std::forward(args)...)); + } + +private: + void increment_ref() const + { + if (m_data) { + m_data->increment_count(); + } + } + + void decrement_ref() const + { + if (!m_data) { + return; + } + + m_data->decrement_count(); + + if (m_data->ref_count() == 0) { + DefaultDelete deleter; + deleter(m_data); + m_data = nullptr; + } + } + +private: + mutable value_type* m_data = nullptr; + + template + friend class RefPtr; +}; + +template +constexpr RefPtr static_pointer_cast(RefPtr const& other) +{ + return RefPtr(static_cast(other.data())); +} + +template +constexpr RefPtr dynamic_pointer_cast(RefPtr const& other) +{ + return RefPtr(dynamic_cast(other.data())); +} + +} diff --git a/Libraries/LibCore/Result.h b/Libraries/LibCore/Result.h index 45959080..d977fe7a 100644 --- a/Libraries/LibCore/Result.h +++ b/Libraries/LibCore/Result.h @@ -58,9 +58,11 @@ class Result { template constexpr Result(U const& data) noexcept requires(std::is_convertible_v) - : m_storage(data) - , m_isError(false) + : m_isError(false) { + // NOTE: use placement new to make "data" the "active" + // union member to prevent UB + new (&m_storage.data) TValue(data); } /** @@ -73,10 +75,18 @@ class Result { requires(std::is_convertible_v) : m_isError(true) { - m_storage.error = error; + // NOTE: use placement new to make "error" the "active" + // union memver to prevent UB + new (&m_storage.error) TError(error); } - constexpr ~Result() noexcept = default; + constexpr ~Result() noexcept { + // NOTE: manually call destructors to prevent UB + if(m_isError) + m_storage.error.~TError(); + else + m_storage.data.~TValue(); + } [[nodiscard]] constexpr bool is_ok() const { diff --git a/TerranEngine/src/Utils/SerializerUtils.h b/Libraries/LibCore/SerializerExtensions.h similarity index 60% rename from TerranEngine/src/Utils/SerializerUtils.h rename to Libraries/LibCore/SerializerExtensions.h index b141704b..0de8d1fe 100644 --- a/TerranEngine/src/Utils/SerializerUtils.h +++ b/Libraries/LibCore/SerializerExtensions.h @@ -1,21 +1,89 @@ #pragma once -#include "LibCore/UUID.h" - -#include "Graphics/Texture.h" +#include "UUID.h" #include #include -namespace TerranEngine { +namespace Terran::Core { + +struct Vec2ToYaml { + explicit Vec2ToYaml(glm::vec2 const& vec) + : data(vec) + { + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, Vec2ToYaml const& v) + { + out << YAML::Flow; + out << YAML::BeginSeq << v.data.x << v.data.y << YAML::EndSeq; + return out; + } + + glm::vec2 const& data; +}; + +struct Vec3ToYaml { + + explicit Vec3ToYaml(glm::vec3 const& vec) + : data(vec) + { + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, Vec3ToYaml const& v) + { + out << YAML::Flow; + out << YAML::BeginSeq << v.data.x << v.data.y << v.data.z << YAML::EndSeq; + return out; + } + + glm::vec3 const& data; +}; -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec3 const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec4 const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, TextureFilter const& v); -YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v); +struct Vec4ToYaml { + + explicit Vec4ToYaml(glm::vec4 const& vec) + : data(vec) + { + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, Vec4ToYaml const& v) + { + out << YAML::Flow; + out << YAML::BeginSeq << v.data.x << v.data.y << v.data.z << v.data.w << YAML::EndSeq; + return out; + } + + glm::vec4 data; +}; + +struct UUIDToYaml { + + explicit UUIDToYaml(Core::UUID const& id) + : data(id) + { + } + friend YAML::Emitter& operator<<(YAML::Emitter& out, UUIDToYaml const& id) + { + out << std::to_string(id.data); + return out; + } + + Core::UUID const& data; +}; +struct ByteToYaml { + + explicit ByteToYaml(std::byte const& byte) + : data(byte) + { + } + + friend YAML::Emitter& operator<<(YAML::Emitter& out, ByteToYaml const& byte) + { + out << static_cast(byte.data); + return out; + } + + std::byte const& data; +}; } @@ -113,23 +181,6 @@ struct convert { } }; -template<> -struct convert { - static Node encode(TerranEngine::TextureFilter const& rhs) - { - Node node; - node.push_back(TerranEngine::TextureFilterToString(rhs)); - return node; - } - - static bool decode(Node const& node, TerranEngine::TextureFilter& rhs) - { - - rhs = TerranEngine::TextureFilterFromString(node.as()); - return true; - } -}; - template<> struct convert { static Node encode(std::byte rhs) diff --git a/Libraries/LibCore/WeakRef.h b/Libraries/LibCore/WeakRef.h new file mode 100644 index 00000000..b9451645 --- /dev/null +++ b/Libraries/LibCore/WeakRef.h @@ -0,0 +1,159 @@ +#pragma once +#include "RefPtr.h" +#include +#include +#include +#include + +namespace Terran::Core { + +template +class WeakPtr final { + using value_type = TValue; + +public: + constexpr WeakPtr() noexcept = default; + explicit(false) constexpr WeakPtr(WeakPtr const& other) noexcept + : m_data(other.m_data) + , m_weak_data(other.m_weak_data) + { + increment_weak_count(); + } + + template + requires(std::is_convertible_v) + explicit(false) constexpr WeakPtr(WeakPtr const& other) noexcept + : m_data(other.m_data) + , m_weak_data(other.m_weak_data) + { + increment_weak_count(); + } + + template + requires(std::is_convertible_v) + explicit(false) constexpr WeakPtr(RefPtr const& other) noexcept + : m_data(other.data()) + , m_weak_data(other.data() ? other.data()->acquire_weak_data() : nullptr) + { + } + + explicit(false) constexpr WeakPtr(WeakPtr&& other) noexcept + : m_data(other.release()) + , m_weak_data(std::exchange(other.m_weak_data, nullptr)) + { + increment_weak_count(); + } + + template + requires(std::is_convertible_v) + explicit(false) constexpr WeakPtr(WeakPtr&& other) noexcept + : m_data(other.release()) + , m_weak_data(std::exchange(other.m_weak_data, nullptr)) + { + increment_weak_count(); + } + + constexpr ~WeakPtr() noexcept + { + decrement_weak_count(); + } + + [[nodiscard]] constexpr value_type* release() noexcept + { + return std::exchange(m_data, nullptr); + } + + [[nodiscard]] constexpr bool expired() const noexcept + { + return !m_weak_data || !m_weak_data->is_alive(); + } + + [[nodiscard]] constexpr RefPtr lock() noexcept + { + if (expired()) { + return RefPtr(nullptr); + } + + return RefPtr(m_data); + } + + constexpr bool operator==(WeakPtr const& other) const noexcept + { + return m_data == other.m_data; + } + + explicit(false) constexpr operator bool() const noexcept + { + return !is_null(); + } + + constexpr bool is_null() const noexcept + { + return !m_data; + } + + constexpr WeakPtr& operator=(WeakPtr const& other) noexcept + { + if (this == other) + return *this; + + m_data = other.m_data; + return *this; + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr& operator=(WeakPtr const& other) noexcept + { + if (this == other) + return *this; + + m_data = other.m_data; + return *this; + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr& operator=(RefPtr const& other) noexcept + { + m_data = other.data(); + return *this; + } + + constexpr WeakPtr& operator=(WeakPtr&& other) noexcept + { + WeakPtr temp { std::move(other) }; + m_data = temp.m_data; + return *this; + } + + template + requires(std::is_convertible_v) + constexpr WeakPtr& operator=(WeakPtr&& other) noexcept + { + WeakPtr temp { std::move(other) }; + m_data = temp.m_data; + return *this; + } + +private: + void increment_weak_count() const noexcept + { + if (m_weak_data) { + m_weak_data->increment_weak_count(); + } + } + + void decrement_weak_count() const noexcept + { + if (m_weak_data) { + m_weak_data->decrement_weak_count(); + } + } + +private: + value_type* m_data = nullptr; + WeakData* m_weak_data = nullptr; +}; + +} diff --git a/Libraries/LibScene/ComponentSerializer.h b/Libraries/LibScene/ComponentSerializer.h new file mode 100644 index 00000000..04a220f2 --- /dev/null +++ b/Libraries/LibScene/ComponentSerializer.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Entity.h" +#include +#include +namespace Terran::World { + +template +concept ComponentSerializer = requires( + T serializer, YAML::Emitter& out, YAML::Node& node, Entity entity) { + { serializer.serialize(out, entity) } -> std::same_as; + { serializer.deserialize(node, entity) } -> std::same_as; +}; + +} diff --git a/Libraries/LibScene/Components.h b/Libraries/LibScene/Components.h new file mode 100644 index 00000000..a9fb5d4c --- /dev/null +++ b/Libraries/LibScene/Components.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include +#include + +namespace Terran::World { + +struct TagComponent { + TagComponent() = default; + + TagComponent(std::string const& name, Core::UUID const& id) + : Name(name) + , Id(id) + { + } + + explicit TagComponent(std::string const& name) + : Name(name) + { + } + + std::string Name; + Core::UUID Id; +}; + +struct TransformComponent { + TransformComponent() = default; + + glm::vec3 Position = { 0.0f, 0.0f, 0.0f }; + glm::vec3 Rotation = { 0.0f, 0.0f, 0.0f }; + glm::vec3 Scale = { 1.0f, 1.0f, 1.0f }; + + glm::vec3 Forward = { 0.0f, 0.0f, 1.0f }; + glm::vec3 Up = { 0.0f, 1.0f, 0.0f }; + glm::vec3 Right = { 1.0f, 0.0f, 0.0f }; + + // dirty flag used for optimization + bool IsDirty = true; + + // cached transform matrices + glm::mat4 WorldSpaceTransformMatrix = glm::mat4(1.0f); + glm::mat4 LocalSpaceTransformMatrix = glm::mat4(1.0f); +}; + +struct RelationshipComponent { + RelationshipComponent() = default; + + Core::UUID Parent = Core::UUID::invalid(); + std::vector Children; +}; + +} diff --git a/Libraries/LibScene/Entity.cpp b/Libraries/LibScene/Entity.cpp new file mode 100644 index 00000000..7ebe4b60 --- /dev/null +++ b/Libraries/LibScene/Entity.cpp @@ -0,0 +1,115 @@ +#include "Entity.h" +#include "Components.h" +#include "Scene.h" + +#include +#include +#include + +#include +#include +#include + +namespace Terran::World { + +bool Entity::valid() const +{ + return m_scene->m_registry.valid(m_handle); +} + +bool Entity::has_parent() const +{ + return has_component() + ? (bool)m_scene->find_entity(get_component().Parent) + : false; +} +Core::UUID const& Entity::scene_id() const +{ + return m_scene->id(); +} + +Entity Entity::child_at(uint32_t index) const +{ + if (!has_component()) + return {}; + + return m_scene->find_entity(children()[index]); +} + +Entity Entity::parent() const +{ + if (!has_component()) + return {}; + + return m_scene->find_entity(parent_id()); +} + +Core::Result Entity::set_parent(Entity parent) +{ + if (!has_component()) + add_component(); + + if (!parent.has_component()) + parent.add_component(); + + if (is_child_of(parent)) { + TR_WARN(TR_LOG_SCENE, "Trying to parent an entity with Id: {} which is already a child of {}", id(), parent.id()); + return { EntityErrors::AlreadyAChildOfThisParent }; + } + if (parent.is_child_of(*this)) { + // NOTE: this is a terrible error message, couldn't think of a better one right now though + TR_WARN(TR_LOG_SCENE, "Trying to parent to an entity {} which is a child of the current entity with Id: {}", parent.id(), id()); + return { EntityErrors::TargetParentIsAlreadyAChildOfThisEntity }; + } + + if (has_parent()) + unparent(); + + auto& relationship_component = get_component(); + relationship_component.Parent = parent.id(); + + auto& parent_relationship_component = parent.get_component(); + parent_relationship_component.Children.emplace_back(id()); + + m_scene->convert_to_local_space(*this); + return {}; +} +Core::Result Entity::unparent() +{ + RelationshipComponent const* relationship_component = try_get_component(); + + if (!relationship_component) { + TR_WARN(TR_LOG_SCENE, "Trying to unparent an enity with Id: {} which doesn't have a relationship component", id()); + return { EntityErrors::DoesntHaveRelationshipComponent }; + } + + Core::UUID parent_id = relationship_component->Parent; + Entity parent = m_scene->find_entity(parent_id); + + if (!parent) { + TR_WARN(TR_LOG_SCENE, "Failed to find parent {} while trying to unparent", parent_id); + return { EntityErrors::ParentNotFound }; + } + + RelationshipComponent* parent_relationship_component = parent.try_get_component(); + + if (!parent_relationship_component) { + TR_WARN(TR_LOG_SCENE, "Parent {} doesn't have relationship component! Failed to unparent", parent_id); + return { EntityErrors::DoesntHaveRelationshipComponent }; + } + + auto& children = parent_relationship_component->Children; + auto const& iterator = std::ranges::find(children, id()); + if (iterator == children.end()) { + TR_WARN(TR_LOG_SCENE, "Parent {} doesn't contain entity: {}! Failed to unparent", parent_id, id()); + return { EntityErrors::ParentDoesntContainChild }; + } + + children.erase(iterator); + set_parent_id(Core::UUID({ 0 })); + + m_scene->convert_to_world_space(*this); + return {}; +} + +} diff --git a/Libraries/LibScene/Entity.h b/Libraries/LibScene/Entity.h new file mode 100644 index 00000000..a5ac94c1 --- /dev/null +++ b/Libraries/LibScene/Entity.h @@ -0,0 +1,167 @@ +#pragma once + +#include "Components.h" + +#include +#include +#include + +#include + +#pragma warning(push) +#pragma warning(disable : 4834) + +#include +#include +#include +#include +#include + +namespace Terran::World { + +class Scene; + +enum class EntityErrors : uint8_t { + AlreadyAChildOfThisParent, + DoesntHaveRelationshipComponent, + TargetParentIsAlreadyAChildOfThisEntity, + ParentNotFound, + ParentDoesntContainChild +}; + +class Entity final { +public: + Entity() = default; + + Entity(entt::entity const& handle, Scene* scene) + : m_handle(handle) + , m_scene(scene) + { + } + + ~Entity() = default; + + template + Component& add_component(Args&&... parameters); + + template + Component& add_or_replace_component(Args&&... parameters); + + template + Component& get_component() const; + + template + void remove_component(); + + template + auto try_get_component() const; + + template + bool has_component() const; + + // visit all the components of an entity + // the signiture of Func should be void(const entt::type_info) + template + void visit(Entity entity, Func func) const; + + // base stuffs + Core::UUID const& id() const + { + return has_component() + ? get_component().Id + : Core::UUID::invalid(); + } + + TransformComponent& transform() const + { + return get_component(); + } + + bool valid() const; + + std::string const& name() const + { + return get_component().Name; + } + + // operators + explicit operator entt::entity() const + { + return m_handle; + } + + explicit operator uint32_t() const + { + return static_cast(m_handle); + } + + explicit operator bool() const + { + return m_handle != entt::null; + } + + bool operator==(Entity const& other) const = default; + + // relationship component stuffs + std::span children() const + { + return has_component() + ? get_component().Children + : std::span(); + } + + Terran::Core::UUID parent_id() const + { + return has_component() ? get_component().Parent : Terran::Core::UUID::invalid(); + } + + bool has_parent() const; + + Terran::Core::UUID const& scene_id() const; + + Entity child_at(uint32_t index) const; + + void set_parent_id(Terran::Core::UUID const& id) const + { + if (!has_component()) + return; + + auto& relComp = get_component(); + relComp.Parent = id; + } + + Entity parent() const; + + bool is_child_of(Entity entity) const + { + if (!has_component()) + return false; + + if (!entity.has_component()) + return false; + + return parent_id() == entity.id(); + } + + Core::Result set_parent(Entity parent); + + Core::Result unparent(); + + void remove_child(Entity child) const + { + child.unparent(); + } + + void reparent(Entity newParent) + { + unparent(); + set_parent(newParent); + } + +private: + entt::entity m_handle { entt::null }; + Scene* m_scene = nullptr; +}; + +} +#pragma warning(pop) diff --git a/Libraries/LibScene/Entity.inl b/Libraries/LibScene/Entity.inl new file mode 100644 index 00000000..4312b3a5 --- /dev/null +++ b/Libraries/LibScene/Entity.inl @@ -0,0 +1,64 @@ +#pragma once +namespace Terran::World { + +template +inline Component& Entity::add_component(Args&&... parameters) +{ + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); + + TR_ASSERT(!has_component(), "Entity already has component"); + + Component& component = m_scene->m_registry.emplace(m_handle, std::forward(parameters)...); + return component; +} + +template +inline Component& Entity::add_or_replace_component(Args&&... parameters) +{ + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); + Component& component = m_scene->m_registry.emplace_or_replace(m_handle, std::forward(parameters)...); + return component; +} + +template +inline Component& Entity::get_component() const +{ + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); + + TR_ASSERT(has_component(), "Entity doesn't have the component"); + return m_scene->m_registry.get(m_handle); +} + +template +inline void Entity::remove_component() +{ + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); + + TR_ASSERT(has_component(), "Entity doesn't have component"); + + m_scene->m_registry.remove(m_handle); +} + +template +inline auto Entity::try_get_component() const +{ + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); + + return m_scene->m_registry.try_get(m_handle); +} + +template +inline bool Entity::has_component() const +{ + TR_ASSERT(m_handle != entt::null, "Ivalid entity"); + + return m_scene->m_registry.all_of(m_handle); +} + +template +inline void Entity::visit(Entity entity, Func func) const +{ + m_scene->m_registry.visit(entity, std::forward(func)); +} + +} diff --git a/Libraries/LibScene/Scene.cpp b/Libraries/LibScene/Scene.cpp new file mode 100644 index 00000000..2fd24bf0 --- /dev/null +++ b/Libraries/LibScene/Scene.cpp @@ -0,0 +1,278 @@ +#include "Scene.h" + +#include "Components.h" +#include "Entity.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include +#include +#include + +namespace Terran::World { + +struct SceneComponent final { + Terran::Core::UUID SceneID; +}; + +namespace { + +template +void copy_component_internal(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) +{ + if (!srcRegistry.all_of(srcHandle)) + return; + + dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); +} + +template +void copy_component_internal(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) +{ + copy_component_internal(srcHandle, dstHandle, srcRegistry, srcRegistry); +} + +glm::mat4 calculate_transform_matrix(TransformComponent const& transform) +{ + return glm::translate(glm::mat4(1.0f), transform.Position) * glm::toMat4(glm::quat(transform.Rotation)) * glm::scale(glm::mat4(1.0f), transform.Scale); +} + +} + +Scene::Scene(Core::EventDispatcher& event_dispatcher) + : Scene(event_dispatcher, Core::UUID()) +{ +} + +Scene::Scene(Core::EventDispatcher& event_dispatcher, Core::UUID const& handle) + : Asset(handle) + , m_event_dispatcher(event_dispatcher) +{ + auto const sceneEntity = m_registry.create(); + m_registry.emplace(sceneEntity, handle); +} + +Scene::~Scene() +{ + m_registry.clear(); +} + +Entity Scene::create_entity(std::string const& name) +{ + return create_entity(name, Terran::Core::UUID()); +} + +Entity Scene::create_entity(std::string const& name, Terran::Core::UUID const& uuid) +{ + entt::entity e = m_registry.create(); + + Entity entity(e, this); + entity.add_component(name.empty() ? "Entity" : name, uuid); + entity.add_component(); + + m_entity_map[uuid] = e; + + sort_entities(); + return entity; +} + +Entity Scene::create_empty_entity() +{ + entt::entity e = m_registry.create(); + Entity entity(e, this); + return entity; +} + +void Scene::destrory_entity(Entity entity, bool first) +{ + if (entity.has_component()) { + if (first && entity.has_parent()) { + entity.parent().remove_child(entity); + } + + for (auto const& child_id : entity.children()) + destrory_entity(find_entity(child_id), false); + } + + if (m_entity_map.contains(entity.id())) + m_entity_map.erase(entity.id()); + + m_registry.destroy((entt::entity)entity); + + sort_entities(); +} + +void Scene::start_runtime() +{ + if (m_is_playing) + return; + + m_is_playing = true; + + SceneStartSimulationEvent start_simulation_event; + m_event_dispatcher.trigger(start_simulation_event); +} + +void Scene::stop_runtime() +{ + if (!m_is_playing) + return; + + m_is_playing = false; + + SceneStopSimulationEvent stop_simulation_event; + m_event_dispatcher.trigger(stop_simulation_event); +} + +void Scene::update(Terran::Core::Time) +{ + update_transform_hierarchy(); +} + +Entity Scene::find_entity(Core::UUID const& uuid) +{ + if (m_entity_map.contains(uuid)) + return Entity(m_entity_map.at(uuid), this); + + return {}; +} + +Entity Scene::find_entity(std::string_view name) +{ + auto const tag_view = m_registry.view(); + + for (auto e : tag_view) { + Entity entity(e, this); + if (entity.name() == name) + return entity; + } + + return {}; +} + +Entity Scene::duplicate_entity(Entity source_entity, Entity parent) +{ + Entity destination_entity = create_entity(source_entity.name() + " Copy"); + + copy_component_internal( + (entt::entity)source_entity, + (entt::entity)destination_entity, + m_registry); + + if (source_entity.has_component()) { + for (int i = 0; i < source_entity.children().size(); i++) { + Entity childEntity = source_entity.child_at(i); + duplicate_entity(childEntity, destination_entity); + } + + if (!parent) + parent = source_entity.parent(); + + if (parent) + destination_entity.set_parent(parent); + } + + return destination_entity; +} + +void Scene::update_transform_hierarchy() +{ + auto transform_view = entities_with(); + + for (auto e : transform_view) { + Entity entity(e, this); + + if (!entity.has_parent()) + update_entity_transform(entity); + } +} + +void Scene::update_entity_transform(Entity entity) +{ + TransformComponent& transform_component = entity.get_component(); + + if (transform_component.IsDirty) { + if (Entity parent = entity.parent()) { + glm::mat4 parentTransform = parent.transform().WorldSpaceTransformMatrix; + transform_component.WorldSpaceTransformMatrix = parentTransform * calculate_transform_matrix(transform_component); + transform_component.LocalSpaceTransformMatrix = glm::inverse(parentTransform) * transform_component.WorldSpaceTransformMatrix; + } else { + transform_component.WorldSpaceTransformMatrix = calculate_transform_matrix(transform_component); + transform_component.LocalSpaceTransformMatrix = transform_component.WorldSpaceTransformMatrix; + } + + glm::quat rotation = transform_component.Rotation; + + transform_component.Forward = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 0.0f, 1.0f))); + transform_component.Up = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 1.0f, 0.0f))); + transform_component.Right = glm::normalize(glm::rotate(rotation, glm::vec3(1.0f, 0.0f, 0.0f))); + } + + for (size_t i = 0; i < entity.children().size(); i++) { + Entity currEntity = entity.child_at(static_cast(i)); + + if (transform_component.IsDirty) + currEntity.transform().IsDirty = true; + + update_entity_transform(currEntity); + } + + transform_component.IsDirty = false; +} + +void Scene::convert_to_local_space(Entity entity) +{ + auto& transform_component = entity.get_component(); + + if (!entity.has_parent()) + return; + + if (transform_component.IsDirty) + update_entity_transform(entity); + + Entity parent = entity.parent(); + auto const& parent_transform_component = parent.transform(); + + // NOTE: have to calculate it because at this point the local space + // and world space transform matrices are equal + glm::mat4 parent_world_matrix = parent_transform_component.WorldSpaceTransformMatrix; + glm::mat4 local_matrix = glm::inverse(parent_world_matrix) * transform_component.WorldSpaceTransformMatrix; + + Core::Math::decompose_transform_matrix(local_matrix, transform_component.Position, transform_component.Rotation, transform_component.Scale); + + transform_component.IsDirty = true; +} + +void Scene::convert_to_world_space(Entity entity) +{ + auto& transform_component = entity.get_component(); + + if (!entity.has_parent()) + return; + + if (transform_component.IsDirty) + update_entity_transform(entity); + + Core::Math::decompose_transform_matrix(transform_component.WorldSpaceTransformMatrix, transform_component.Position, transform_component.Rotation, transform_component.Scale); + + transform_component.IsDirty = true; +} + +void Scene::sort_entities() +{ + m_registry.sort([](entt::entity const& lEntity, entt::entity const& rEntity) { return lEntity < rEntity; }); +} + +} diff --git a/Libraries/LibScene/Scene.h b/Libraries/LibScene/Scene.h new file mode 100644 index 00000000..0808f567 --- /dev/null +++ b/Libraries/LibScene/Scene.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "Entity.h" + +#pragma warning(push) +#pragma warning(disable : 26439) +#include +#pragma warning(pop) +#include + +#include +#include +#include + +namespace Terran::World { + +class Scene final : public Asset::Asset { +public: + explicit Scene(Core::EventDispatcher& event_dispatcher); + Scene(Core::EventDispatcher& event_dispatcher, Core::UUID const& handle); + ~Scene() override; + + TR_DECLARE_ASSET_TYPE(Scene) + + Entity create_entity(std::string const& name = std::string()); + Entity create_entity(std::string const& name, Core::UUID const& uuid); + Entity create_empty_entity(); + + void destrory_entity(Entity entity, bool first = true); + + void start_runtime(); + void stop_runtime(); + + void update(Core::Time time); + + Entity find_entity(Core::UUID const& uuid); + Entity find_entity(std::string_view name); + + template + auto entities_with(entt::exclude_t exclude = {}) + { + return m_registry.view(exclude); + } + + std::unordered_map& entity_map() + { + return m_entity_map; + } + + Entity duplicate_entity(Entity source_entity, Entity parent = {}); + + bool playing() const + { + return m_is_playing; + } + + + void update_transform_hierarchy(); + void update_entity_transform(Entity entity); + + void convert_to_local_space(Entity entity); + void convert_to_world_space(Entity entity); + + void sort_entities(); + +private: + + template + static void copy_component(Entity source_entity, Entity destination_entity, entt::registry& source_registry, entt::registry& destination_registry) + { + if (!source_registry.all_of((entt::entity)source_entity)) { + return; + } + + destination_registry.emplace_or_replace( + (entt::entity)destination_entity, + source_registry.get((entt::entity)source_entity)); + } + + bool m_is_playing = false; + + std::unordered_map m_entity_map; + Core::EventDispatcher& m_event_dispatcher; + + entt::registry m_registry; + + friend class Entity; + friend class SceneSerializer; + friend class SceneManager; +}; + +} +#include "Entity.inl" diff --git a/Libraries/LibScene/SceneEvent.h b/Libraries/LibScene/SceneEvent.h new file mode 100644 index 00000000..1e491fd0 --- /dev/null +++ b/Libraries/LibScene/SceneEvent.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Scene.h" + +#include +#include +#include +#include + +namespace Terran::World { + +class SceneTransitionEvent { +public: + SceneTransitionEvent(Core::WeakPtr const& oldScene, Core::RefPtr const& newScene) + : m_old_scene(oldScene) + , m_new_scene(newScene) + { + } + + Core::WeakPtr old_scene() const + { + return m_old_scene; + } + + Core::RefPtr new_scene() const + { + return m_new_scene; + } + +private: + Core::WeakPtr m_old_scene; + Core::RefPtr m_new_scene; +}; + +class SceneStartSimulationEvent { +public: + SceneStartSimulationEvent() = default; +}; + +class SceneStopSimulationEvent { +public: + SceneStopSimulationEvent() = default; +}; + +} diff --git a/Libraries/LibScene/SceneManager.cpp b/Libraries/LibScene/SceneManager.cpp new file mode 100644 index 00000000..f8aef4f0 --- /dev/null +++ b/Libraries/LibScene/SceneManager.cpp @@ -0,0 +1,62 @@ +#include "SceneManager.h" +#include "Components.h" +#include "Entity.h" +#include "Scene.h" +#include "SceneEvent.h" + +#include +#include +#include + +namespace Terran::World { + +Core::RefPtr SceneManager::create_empty_scene() +{ + return m_asset_system->asset_manager()->create_asset(event_dispatcher); +} + +Core::RefPtr SceneManager::copy_scene(Core::RefPtr const& source_scene) +{ + Core::RefPtr scene = m_asset_system->asset_manager()->create_asset(event_dispatcher); + + auto tag_view = source_scene->entities_with(); + + for (auto e : tag_view) { + Entity source_entity(e, source_scene.data()); + scene->create_entity(source_entity.name(), source_entity.id()); + } + for (auto e : tag_view) { + + Entity source_entity(e, source_scene.data()); + Entity destination_entity = scene->find_entity(source_entity.id()); + + Scene::copy_component( + source_entity, + destination_entity, + source_scene->m_registry, + scene->m_registry); + Scene::copy_component( + source_entity, + destination_entity, + source_scene->m_registry, + scene->m_registry); + } + + scene->sort_entities(); + + return scene; +} + +void SceneManager::set_current_scene(Core::RefPtr new_scene) +{ + if (m_current_scene == new_scene) { + return; + } + + SceneTransitionEvent scene_transition_event(m_current_scene, new_scene); + event_dispatcher.trigger(scene_transition_event); + + m_current_scene = new_scene; +} + +} diff --git a/Libraries/LibScene/SceneManager.h b/Libraries/LibScene/SceneManager.h new file mode 100644 index 00000000..40e673e3 --- /dev/null +++ b/Libraries/LibScene/SceneManager.h @@ -0,0 +1,47 @@ +#pragma once + +#include "Scene.h" +#include "SceneTypes.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace Terran::World { + +class SceneManager final : public Core::Layer { + +public: + SceneManager(Core::EventDispatcher& dispatcher, Core::RawPtr asset_system) + : Core::Layer("Scene", dispatcher) + , m_asset_system(asset_system) + { + Core::Log::add_logger(TR_LOG_SCENE); + } + + Core::RefPtr create_empty_scene(); + + Core::RefPtr const& current_scene() const + { + return m_current_scene; + } + + void reset_current_scene() + { + m_current_scene.reset(); + } + + void set_current_scene(Core::RefPtr new_scene); + Core::RefPtr copy_scene(Core::RefPtr const& source_scene); + +private: + Core::RefPtr m_current_scene; + Core::RawPtr m_asset_system; +}; + +} diff --git a/Libraries/LibScene/SceneSerializer.cpp b/Libraries/LibScene/SceneSerializer.cpp new file mode 100644 index 00000000..e1dcae04 --- /dev/null +++ b/Libraries/LibScene/SceneSerializer.cpp @@ -0,0 +1,201 @@ +#include "SceneSerializer.h" +#include "Components.h" +#include "Entity.h" +#include "SceneManager.h" +#include "SceneSerializerError.h" +#include "SceneTypes.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include + +using namespace Terran::Core; +namespace Terran::World { + +static char const* const SerializerVersion = "tr-0.1"; + +static void serialize_entity(YAML::Emitter& out, Entity entity) +{ + TR_ASSERT(entity.has_component(), "Can't serialize an entity that doesn't have a tag component"); + + out << YAML::BeginMap; + out << YAML::Key << "Entity" << YAML::Value << entity.id(); + auto const& tag_component = entity.get_component(); + out << YAML::Key << "TagComponent"; + out << YAML::BeginMap; + out << YAML::Key << "Tag" << YAML::Value << tag_component.Name; + out << YAML::EndMap; + + if (entity.has_component()) { + auto const& transform_component = entity.transform(); + out << YAML::Key << "TransformComponent"; + out << YAML::BeginMap; + out << YAML::Key << "Position" << YAML::Value << Vec3ToYaml(transform_component.Position); + out << YAML::Key << "Scale" << YAML::Value << Vec3ToYaml(transform_component.Scale); + out << YAML::Key << "Rotation" << YAML::Value << Vec3ToYaml(transform_component.Rotation); + out << YAML::EndMap; + } + + if (entity.has_component()) { + auto const& relationship_component = entity.get_component(); + + out << YAML::Key << "RelationshipComponent"; + out << YAML::BeginMap; + + out << YAML::Key << "Children" << YAML::Value << YAML::BeginSeq; + for (auto child : relationship_component.Children) + out << child; + out << YAML::EndSeq; + + out << YAML::Key << "Parent"; + if (relationship_component.Parent) + out << YAML::Value << relationship_component.Parent; + else + out << YAML::Null; + + out << YAML::EndMap; + } + out << YAML::EndMap; +} + +bool SceneSerializer::save(Asset::AssetMetadata const& assetMetadata, Core::RefPtr const& asset) +{ + Core::RefPtr scene = Core::dynamic_pointer_cast(asset); + if (!scene) + return false; + + YAML::Emitter out; + + out << YAML::BeginMap; + + out << YAML::Key << "SerializerVersion" << YAML::Value << SerializerVersion; + out << YAML::Key << "Entities"; + out << YAML::BeginSeq; + + auto const tagComponentView = scene->entities_with(); + for (auto e : tagComponentView) { + Entity entity(e, scene.data()); + serialize_entity(out, entity); + } + + out << YAML::EndSeq; + out << YAML::EndMap; + + std::ofstream ofs(assetMetadata.Path); + ofs << out.c_str(); + return true; +} + +static YAML::const_iterator find_entity_node(YAML::Node const& scene, Terran::Core::UUID const& entityID) +{ + for (auto it = scene.begin(); it != scene.end(); ++it) { + auto const& entity = *it; + Terran::Core::UUID id = entity["Entity"].as(); + if (id == entityID) + return it; + } + + return scene.end(); +} + +static Entity deserialize_entity(YAML::Node const& data, YAML::Node const& scene, Core::RefPtr deserializedScene) +{ + try { + Core::UUID id = data["Entity"].as(); + if (!id) { + TR_ERROR(TR_LOG_SCENE, "Invalid id"); + TR_ASSERT(false, "Invalid id"); + return {}; + } + + if (Entity entity = deserializedScene->find_entity(id); entity) + return entity; + + auto tag_component_node = data["TagComponent"]; + if (!tag_component_node) { + TR_ASSERT(false, "Invalid tag component"); + return {}; + } + + std::string name = tag_component_node["Tag"].as(); + Entity deserialized_entity = deserializedScene->create_entity(name, id); + + if (auto transform_component_node = data["TransformComponent"]; transform_component_node) { + auto& transform_component = deserialized_entity.transform(); + transform_component.Position = transform_component_node["Position"].as(glm::vec3(0.0f, 0.0f, 0.0f)); + transform_component.Rotation = transform_component_node["Rotation"].as(glm::vec3(0.0f, 0.0f, 0.0f)); + transform_component.Scale = transform_component_node["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); + } + + if (auto relationship_component_node = data["RelationshipComponent"]; relationship_component_node) { + deserialized_entity.add_component(); + for (auto child_id_node : relationship_component_node["Children"]) { + Core::UUID deserialized_child_id = child_id_node.as(); + Entity child = deserializedScene->find_entity(deserialized_child_id); + + if (child) { + child.set_parent(deserialized_entity); + continue; + } + + YAML::const_iterator child_node = find_entity_node(scene, deserialized_child_id); + if (child_node == scene.end()) { + TR_WARN(TR_LOG_SCENE, "Entity {} references child entity with Id: {} could not be found! Child is skipped!", id, deserialized_child_id); + } else { + child = deserialize_entity(*child_node, scene, deserializedScene); + } + + child.set_parent(deserialized_entity); + } + } + + return deserialized_entity; + } catch (YAML::InvalidNode const& ex) { + TR_ERROR(TR_LOG_CORE, ex.what()); + return Entity(); + } +} + +Asset::AssetLoadResult SceneSerializer::load(Asset::AssetMetadata const& assetMetadata) +{ + YAML::Node data; + try { + data = YAML::LoadFile(assetMetadata.Path); + } catch (YAML::ParserException const& ex) { + TR_ERROR(TR_LOG_SCENE, ex.what()); + return { Core::CreateShared(SceneSerializerError::Code::InvalidFormat, ex.what()) }; + } catch (YAML::BadFile const& ex) { + TR_ERROR(TR_LOG_SCENE, ex.what()); + return { Core::CreateShared(SceneSerializerError::Code::NotFound, ex.what()) }; + } + + Core::RefPtr scene = m_scene_system.create_empty_scene(); + + if (auto entities = data["Entities"]; entities) { + for (auto entity : entities) { + if (!deserialize_entity(entity, entities, scene)) + return { Core::CreateShared(SceneSerializerError::Code::InvalidFormat) }; + } + } + + return scene; +} + +} diff --git a/Libraries/LibScene/SceneSerializer.h b/Libraries/LibScene/SceneSerializer.h new file mode 100644 index 00000000..c4f246e1 --- /dev/null +++ b/Libraries/LibScene/SceneSerializer.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Scene.h" +#include "SceneManager.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace Terran::World { + +class SceneSerializer final : public Asset::AssetImporter { +public: + explicit SceneSerializer(SceneManager& scene_system) + : m_scene_system(scene_system) + { + } + + ~SceneSerializer() override = default; + bool save(Asset::AssetMetadata const& assetMetadata, Core::RefPtr const& asset) override; + Asset::AssetLoadResult load(Asset::AssetMetadata const& assetMetadata) override; + [[nodiscard]] bool can_handle(std::filesystem::path const& assetPath) override + { + return assetPath.extension() == ".terran"; + } + [[nodiscard]] Asset::AssetTypeId asset_type() override + { + return Scene::static_type(); + } + +private: + SceneManager& m_scene_system; +}; + +} diff --git a/Libraries/LibScene/SceneSerializerError.h b/Libraries/LibScene/SceneSerializerError.h new file mode 100644 index 00000000..cd74accc --- /dev/null +++ b/Libraries/LibScene/SceneSerializerError.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include +#include +namespace Terran::World { + +class SceneSerializerError final : public Asset::AssetError { +public: + enum class Code : uint8_t { + InvalidFormat, + NotFound, + }; + + explicit SceneSerializerError(Code code, std::string_view details = "") + : m_code(code) + , m_details(details) + { + } + + ~SceneSerializerError() override = default; + + [[nodiscard]] std::string_view message() const override + { + switch (m_code) { + case Code::InvalidFormat: + return "Scene file has invalid format"; + case Code::NotFound: + return "Scene file was not found"; + } + + return "Unknown error"; + } + + [[nodiscard]] std::string_view source() const override + { + return m_details; + } + +private: + Code m_code; + std::string_view m_details; +}; + +} diff --git a/Libraries/LibScene/SceneTypes.h b/Libraries/LibScene/SceneTypes.h new file mode 100644 index 00000000..af93f2dd --- /dev/null +++ b/Libraries/LibScene/SceneTypes.h @@ -0,0 +1,3 @@ +#pragma once + +constexpr char const* const TR_LOG_SCENE = "Scene"; diff --git a/Libraries/LibScene/premake5.lua b/Libraries/LibScene/premake5.lua new file mode 100644 index 00000000..7829e517 --- /dev/null +++ b/Libraries/LibScene/premake5.lua @@ -0,0 +1,63 @@ +project "LibScene" +pic "On" +kind "StaticLib" +language "C++" +cppdialect "C++20" +staticruntime "Off" + +targetdir("%{prj.location}/bin/" .. outputdir) +objdir("%{prj.location}/bin-int/" .. outputdir) + +files { + "**.h", + "**.cpp", + + "vendor/glm/glm/**.hpp", + "vendor/glm/glm/**.inl", +} + +externalincludedirs { + "%{wks.location}/Libraries", + "%{Dependencies.spdlog.include}", + "%{Dependencies.glm.include}", + "%{Dependencies.yaml.include}", + "%{Dependencies.entt.include}", +} + +linkoptions { + "-IGNORE:4006", +} + +filter "system:macosx" +architecture "ARM64" + +libdirs { "/usr/local/lib" } +links { + "CoreFoundation.framework", -- no path needed for system frameworks +} + +filter "system:windows" +architecture "x86_64" +systemversion "latest" + +defines { + "_CRT_SECURE_NO_WARNINGS", +} + +includedirs { + "%{IncludeDirectories.optick}", +} + +links { + "OptickCore", +} + +filter "configurations:Debug" +defines "TR_DEBUG" +runtime "Debug" +symbols "on" + +filter "configurations:Release" +defines "TR_RELEASE" +runtime "Release" +optimize "on" diff --git a/Libraries/LibWindow/GLFWWindow.cpp b/Libraries/LibWindow/GLFWWindow.cpp index bbf6f0a7..060edd72 100644 --- a/Libraries/LibWindow/GLFWWindow.cpp +++ b/Libraries/LibWindow/GLFWWindow.cpp @@ -1,26 +1,30 @@ #include "GLFWWindow.h" -#include "LibCore/Assert.h" - -#include "GamepadEvent.h" #include "KeyboardEvent.h" #include "MouseEvent.h" #include "WindowEvent.h" +#include "Window.h" +#include "WindowTypes.h" +#include "MouseButtons.h" +#include "KeyCodes.h" + +#include #include -#include +#include + +#include + +#include namespace Terran { namespace Window { namespace Implementation { -static int s_glfw_init_result = glfwInit(); static GLFWwindow* s_current_window = nullptr; GLFWWindow::GLFWWindow(Core::EventDispatcher& event_dispatcher, WindowData const& data) : Window(event_dispatcher) { - TR_ASSERT(s_glfw_init_result, "GFLW couldn't initialze!"); - init(data); } GLFWWindow::~GLFWWindow() @@ -71,16 +75,17 @@ void GLFWWindow::init(WindowData data) m_window_data_ptr.video_mode = vidMode; m_window = glfwCreateWindow(data.Width, data.Height, data.Name.data(), nullptr, nullptr); - s_current_window = m_window; TR_ASSERT(m_window, "Couldn't create a GLFW window!"); + s_current_window = m_window; glfwSetWindowUserPointer(m_window, &m_window_data_ptr); glfwGetWindowContentScale(m_window, &m_window_data_ptr.scale_x, &m_window_data_ptr.scale_y); - TR_CORE_INFO(TR_LOG_CORE, "Created window"); + TR_INFO(TR_LOG_WINDOW, "Created window {} {}x{}", data.Name, data.Width, data.Height); + TR_TRACE(TR_LOG_WINDOW, "Setting up window event handlers"); setup_callbacks(); - TR_CORE_INFO(TR_LOG_CORE, "Setup window events"); + TR_INFO(TR_LOG_WINDOW, "Window event handlers successfuly setup"); glfwMakeContextCurrent(m_window); set_vsync(data.VSync); @@ -190,8 +195,7 @@ void GLFWWindow::setup_callbacks() void GLFWWindow::destroy() { glfwDestroyWindow(m_window); - glfwTerminate(); - TR_CORE_INFO(TR_LOG_CORE, "Destroyed window and opengl context"); + TR_INFO(TR_LOG_WINDOW, "Destroyed window"); } } diff --git a/Libraries/LibWindow/Input.h b/Libraries/LibWindow/Input.h index 03b9a592..fd67f23e 100644 --- a/Libraries/LibWindow/Input.h +++ b/Libraries/LibWindow/Input.h @@ -4,6 +4,7 @@ #include "InputState.h" #include "KeyCodes.h" #include "MouseButtons.h" +#include "WindowTypes.h" #include #include @@ -109,7 +110,7 @@ class Input final { for (auto const& key : InputUtils::Keys) m_keyStates.emplace(key, InputState()); - TR_CORE_INFO(TR_LOG_CORE, "Initialized input system"); + TR_INFO(TR_LOG_WINDOW, "Initialized input system"); } key_state_map m_keyStates; diff --git a/Libraries/LibWindow/WindowSystem.cpp b/Libraries/LibWindow/WindowSystem.cpp index b93e2bc7..732dc75b 100644 --- a/Libraries/LibWindow/WindowSystem.cpp +++ b/Libraries/LibWindow/WindowSystem.cpp @@ -1,10 +1,12 @@ #include "WindowSystem.h" -#include "GamepadEvent.h" #include "ControllerInput.h" +#include "GamepadEvent.h" +#include "WindowTypes.h" #include #include #include +#include #include #include @@ -21,22 +23,38 @@ WindowSystem::WindowSystem(Core::EventDispatcher& dispatcher) : Core::Layer("WindowLayer", dispatcher) { s_event_dispatcher = &dispatcher; + Core::Log::add_logger(TR_LOG_WINDOW); } WindowSystem::~WindowSystem() { } +static void window_error_callback(int error, char const* description) +{ + TR_ERROR(TR_LOG_WINDOW, "{}: {}", error, description); +} Core::Result WindowSystem::on_attach() { // we want hats and buttons to be queried using different functions // without this querying a joystick buttons' state will also give us // its hats' state + TR_TRACE(TR_LOG_WINDOW, "Intializing GLFW..."); + glfwSetErrorCallback(window_error_callback); glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); - if (glfwInit() != GLFW_TRUE) + if (glfwInit() != GLFW_TRUE) { + char const* error_description; + int error_code = glfwGetError(&error_description); + if (error_description) { + TR_ERROR(TR_LOG_WINDOW, "Couldn't initialize GLFW! {}: {}", error_code, error_description); + } else { + TR_ERROR(TR_LOG_WINDOW, "Couldn't initialize GLFW! {}", error_code); + } return Core::Bad(); + } m_controllerInput.initialize(); + TR_INFO(TR_LOG_WINDOW, "GLFW successfuly initialized!"); // maybe not the greatest solution??? // for now though i still want the users to be able to access the controller input through the WindowSystem @@ -61,7 +79,11 @@ Core::Result WindowSystem::on_dettach() { // if glfwInit was unsuccessful running this function does nothing // thus there is no need to conditionally run it + m_windows.clear(); + + TR_TRACE(TR_LOG_WINDOW, "Terminating GLFW..."); glfwTerminate(); + TR_INFO(TR_LOG_WINDOW, "GLFW successfuly terminated!"); return {}; } diff --git a/Libraries/LibWindow/WindowSystem.h b/Libraries/LibWindow/WindowSystem.h index 9676d66f..dc19301c 100644 --- a/Libraries/LibWindow/WindowSystem.h +++ b/Libraries/LibWindow/WindowSystem.h @@ -37,6 +37,11 @@ class WindowSystem : public Core::Layer { return m_windows.at(id); } + void destroy(Core::UUID const& id) + { + m_windows.erase(id); + } + constexpr ControllerInput const& controllers() const { return m_controllerInput; diff --git a/Libraries/LibWindow/WindowTypes.h b/Libraries/LibWindow/WindowTypes.h new file mode 100644 index 00000000..f168798f --- /dev/null +++ b/Libraries/LibWindow/WindowTypes.h @@ -0,0 +1,3 @@ +#pragma once + +constexpr char const* const TR_LOG_WINDOW = "Window"; diff --git a/Sandbox/premake5.lua b/Sandbox/premake5.lua index cc20dc94..eae64d86 100644 --- a/Sandbox/premake5.lua +++ b/Sandbox/premake5.lua @@ -20,9 +20,10 @@ externalincludedirs { "%{Dependencies.spdlog.include}", "%{IncludeDirectories.imgui}", "%{Dependencies.glm.include}", - "%{IncludeDirectories.entt}", "%{IncludeDirectories.imguizmo}", + "%{Dependencies.entt.include}", "%{IncludeDirectories.optick}", + "%{Dependencies.yaml.include}", "%{wks.location}/TerranEditor/vendor/FontAwesome", } @@ -30,6 +31,8 @@ links { "LibCore", "LibMain", "LibWindow", + "LibScene", + "LibAsset", } defines { diff --git a/Sandbox/src/SandboxLayer.cpp b/Sandbox/src/SandboxLayer.cpp index 20274530..ad399456 100644 --- a/Sandbox/src/SandboxLayer.cpp +++ b/Sandbox/src/SandboxLayer.cpp @@ -1,17 +1,22 @@ #include "SandboxLayer.h" +#include "LibAsset/AssetMetadata.h" +#include "LibCore/Log.h" +#include "LibScene/Components.h" #include +#include #include #include #include -#include #include +#include +#include #define GLM_ENABLE_EXPERIMENTAL #include -#include #include +#include #include #include @@ -34,18 +39,6 @@ static void TestFunc() TR_CLIENT_WARN("Some warning"); } -struct TestStruct { - virtual void test() { - TR_CLIENT_INFO("It works!"); - } -}; - -struct TestStruct2 : public TestStruct { - virtual void test() override { - TR_CLIENT_WARN("It doesn't work!!!!!"); - } -}; - SandboxLayer::SandboxLayer(Terran::Core::EventDispatcher& event_dispatcher, Terran::Core::RawPtr windowSystem) : Layer("Sandbox Layer", event_dispatcher) , m_windowSystem(windowSystem) @@ -59,6 +52,7 @@ SandboxLayer::SandboxLayer(Terran::Core::EventDispatcher& event_dispatcher, Terr auto const& window = m_windowSystem->window(windowId); event_dispatcher.handlers().connect<&SandboxLayer::on_window_close>(this); + } Core::Result SandboxLayer::on_attach() diff --git a/TerranEngine/premake5.lua b/TerranEngine/premake5.lua index 70cae9ec..ccfb5e34 100644 --- a/TerranEngine/premake5.lua +++ b/TerranEngine/premake5.lua @@ -67,7 +67,7 @@ includedirs { "%{IncludeDirectories.imgui}", "%{IncludeDirectories.glad}", "%{IncludeDirectories.stb}", - "%{IncludeDirectories.entt}", + "%{Dependencies.entt.include}", "%{IncludeDirectories.msdfgen}", "%{IncludeDirectories.msdf_atlas_gen}", "%{IncludeDirectories.box2d}", diff --git a/TerranEngine/src/Events/SceneEvent.h b/TerranEngine/src/Events/SceneEvent.h deleted file mode 100644 index d8c63c6f..00000000 --- a/TerranEngine/src/Events/SceneEvent.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "LibCore/Event.h" -#include "LibCore/Base.h" - -#include "Scene/Scene.h" - -namespace TerranEngine { - -class SceneTransitionEvent final : public Terran::Core::Event { -public: - SceneTransitionEvent(Terran::Core::Weak const& oldScene, Terran::Core::Shared const& newScene) - : m_OldScene(oldScene) - , m_NewScene(newScene) - { - } - - EVENT_CLASS_TYPE(SceneTransitionEvent) - EVENT_CLASS_CATEGORY(EventCategoryApplication) - - Terran::Core::Weak GetOldScene() { return m_OldScene; } - Terran::Core::Shared GetNewScene() { return m_NewScene; } - -private: - Terran::Core::Weak m_OldScene; - Terran::Core::Shared m_NewScene; -}; - -} diff --git a/TerranEngine/src/Math/Math.cpp b/TerranEngine/src/Math/Math.cpp deleted file mode 100644 index 40591421..00000000 --- a/TerranEngine/src/Math/Math.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "trpch.h" -#include "Math.h" - -#include -#define GLM_ENABLE_EXPERIMENTAL -#include -#include - -namespace TerranEngine -{ - bool Math::Decompose(const glm::mat4& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale) - { - glm::mat4 LocalMatrix(modelMatrix); - - // Normalize the matrix. - if (glm::epsilonEqual(LocalMatrix[3][3], static_cast(0), glm::epsilon())) - return false; - - for (glm::length_t i = 0; i < 4; ++i) - for (glm::length_t j = 0; j < 4; ++j) - LocalMatrix[i][j] /= LocalMatrix[3][3]; - - if ( - glm::epsilonNotEqual(LocalMatrix[0][3], static_cast(0), glm::epsilon()) || - glm::epsilonNotEqual(LocalMatrix[1][3], static_cast(0), glm::epsilon()) || - glm::epsilonNotEqual(LocalMatrix[2][3], static_cast(0), glm::epsilon())) - { - LocalMatrix[0][3] = LocalMatrix[1][3] = LocalMatrix[2][3] = static_cast(0); - LocalMatrix[3][3] = static_cast(1); - } - - translation = glm::vec3(LocalMatrix[3]); - LocalMatrix[3] = glm::vec4(0, 0, 0, LocalMatrix[3].w); - - glm::vec3 Row[3], Pdum3; - - for (glm::length_t i = 0; i < 3; ++i) - for (glm::length_t j = 0; j < 3; ++j) - Row[i][j] = LocalMatrix[i][j]; - - // get X scale factor and normalize first row. - scale.x = glm::length(Row[0]); - - Row[0] = glm::detail::scale(Row[0], static_cast(1)); - - // get Y scale and normalize 2nd row. - scale.y = glm::length(Row[1]); - Row[1] = glm::detail::scale(Row[1], static_cast(1)); - - // get Z scale and normalize 3rd row. - scale.z = glm::length(Row[2]); - Row[2] = glm::detail::scale(Row[2], static_cast(1)); - - Pdum3 = cross(Row[1], Row[2]); - if (dot(Row[0], Pdum3) < 0) - { - for (glm::length_t i = 0; i < 3; i++) - { - scale[i] *= static_cast(-1); - Row[i] *= static_cast(-1); - } - } - - // get the rotations out. - rotation.y = asin(-Row[0][2]); - if (cos(rotation.y) != 0) { - rotation.x = atan2(Row[1][2], Row[2][2]); - rotation.z = atan2(Row[0][1], Row[0][0]); - } - else { - rotation.x = atan2(-Row[2][0], Row[1][1]); - rotation.z = 0; - } - - return true; - } - - glm::mat4 Math::ComposeTransformationMatrix(const glm::vec3& translation, const glm::vec3& rotation, const glm::vec3& scale) - { - return glm::translate(glm::mat4(1.0f), translation) * - glm::toMat4(glm::quat(rotation)) * - glm::scale(glm::mat4(1.0f), scale); - } -} diff --git a/TerranEngine/src/Math/Math.h b/TerranEngine/src/Math/Math.h deleted file mode 100644 index a9455f8d..00000000 --- a/TerranEngine/src/Math/Math.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -namespace TerranEngine -{ - namespace Math - { - bool Decompose(const glm::mat4& modelMatrix, glm::vec3& translation, glm::vec3& rotation, glm::vec3& scale); - glm::mat4 ComposeTransformationMatrix(const glm::vec3& translation, const glm::vec3& rotation, const glm::vec3& scale); - } - -} \ No newline at end of file diff --git a/TerranEngine/src/Scene/Components.h b/TerranEngine/src/Scene/Components.h deleted file mode 100644 index 6af04a55..00000000 --- a/TerranEngine/src/Scene/Components.h +++ /dev/null @@ -1,177 +0,0 @@ -#pragma once - -#include "LibCore/Base.h" -#include "LibCore/UUID.h" - -#include "Graphics/Font.h" -#include "Graphics/OrthographicCamera.h" - -#include "Physics/PhysicsStates.h" - -#include -#define GLM_ENABLE_EXPERIMENTAL -#include - -#include -#include -#include - -namespace TerranEngine { - -struct TagComponent { - TagComponent() = default; - - TagComponent(std::string const& name, Terran::Core::UUID const& id) - : Name(name) - , ID(id) - { - } - - TagComponent(std::string const& name) - : Name(name) - { - } - - std::string Name; - Terran::Core::UUID ID; -}; - -struct TransformComponent { - TransformComponent() = default; - - glm::vec3 Position = { 0.0f, 0.0f, 0.0f }; - glm::vec3 Rotation = { 0.0f, 0.0f, 0.0f }; - glm::vec3 Scale = { 1.0f, 1.0f, 1.0f }; - - glm::vec3 Forward = { 0.0f, 0.0f, 1.0f }; - glm::vec3 Up = { 0.0f, 1.0f, 0.0f }; - glm::vec3 Right = { 1.0f, 0.0f, 0.0f }; - - // dirty flag used for optimization - bool IsDirty = true; - - // cached transform matrices - glm::mat4 WorldSpaceTransformMatrix = glm::mat4(1.0f); - glm::mat4 LocalSpaceTransformMatrix = glm::mat4(1.0f); -}; - -struct CameraComponent { - CameraComponent() = default; - - OrthographicCamera Camera; - bool Primary = true; - - glm::vec4 BackgroundColor = { 0.1f, 0.1f, 0.1f, 1.0f }; -}; - -struct SpriteRendererComponent { - SpriteRendererComponent() = default; - - glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; - Terran::Core::UUID TextureHandle = Terran::Core::UUID::invalid(); - - int ZIndex = 0; -}; - -struct CircleRendererComponent { - CircleRendererComponent() = default; - - glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; - float Thickness = 1.0f; -}; - -// bullshit; fix -struct LineRendererComponent { - LineRendererComponent() = default; - - glm::vec4 Color = { 1.0f, 1.0f, 1.0f, 1.0f }; - float Thickness = 1.0f; - - glm::vec3 StartPoint = { 0.0f, 0.0f, 0.0f }; - glm::vec3 EndPoint = { 0.0f, 0.0f, 1.0f }; -}; - -struct TextRendererComponent { - TextRendererComponent() = default; - ~TextRendererComponent() = default; - - Terran::Core::Shared FontAtlas; - glm::vec4 TextColor = { 1.0f, 1.0f, 1.0f, 1.0f }; - std::string Text = ""; - float LineSpacing = 1.0f; - float LineWidth = 10.0f; -}; - -struct RelationshipComponent { - RelationshipComponent() - : Parent({ 0 }) - { - } - - Terran::Core::UUID Parent; - std::vector Children; -}; - -struct ScriptComponent { - ScriptComponent() = default; - - ScriptComponent(std::string const& moduleName) - : ModuleName(moduleName) - { - } - - // NOTE: think about having an array of scripts so that one entity - // "can" have more than one script (because of the 1 component of a type per entity) - - std::string ModuleName; - std::vector FieldHandles; - - Terran::Core::UUID ScriptSourceHandle = Terran::Core::UUID::invalid(); - bool ClassExists = true; -}; - -struct Rigidbody2DComponent { - Rigidbody2DComponent() = default; - - PhysicsBodyType BodyType = PhysicsBodyType::Dynamic; - PhysicsBodySleepState SleepState = PhysicsBodySleepState::Awake; - - bool FixedRotation = false; - float GravityScale = 1.0f; - bool Enabled = true; - int LayerIndex = 0; - - Terran::Core::UUID PhysicsMaterialHandle = Terran::Core::UUID::invalid(); -}; - -struct BoxCollider2DComponent { - BoxCollider2DComponent() = default; - - glm::vec2 Offset = { 0.0f, 0.0f }; - glm::vec2 Size = { 1.0f, 1.0f }; - bool Sensor = false; - - uint32_t ColliderIndex = 0; -}; - -struct CircleCollider2DComponent { - CircleCollider2DComponent() = default; - - glm::vec2 Offset = { 0.0f, 0.0f }; - float Radius = 0.5f; - bool Sensor = false; - - uint32_t ColliderIndex = 0; -}; - -struct CapsuleCollider2DComponent { - CapsuleCollider2DComponent() = default; - - glm::vec2 Offset = { 0.0f, 0.0f }; - glm::vec2 Size = { 0.5f, 1.0f }; - bool Sensor = false; - - uint32_t ColliderIndex = 0; -}; - -} diff --git a/TerranEngine/src/Scene/Entity.h b/TerranEngine/src/Scene/Entity.h deleted file mode 100644 index 564ac0d4..00000000 --- a/TerranEngine/src/Scene/Entity.h +++ /dev/null @@ -1,236 +0,0 @@ -#pragma once - -#include "Components.h" -#include "Scene.h" - -#include "LibCore/Assert.h" -#include "LibCore/UUID.h" - -#pragma warning(push) -#pragma warning(disable : 4834) - -#include -#include -#include -#include -#include - -namespace TerranEngine { - -class Entity final { -public: - Entity() = default; - - Entity(entt::entity const& handle, Scene* scene) - : m_Handle(handle) - , m_Scene(scene) - { - } - - ~Entity() = default; - - template - Component& AddComponent(Args&&... parameters) - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - TR_ASSERT(!HasComponent(), "Entity already has component"); - - Component& component = m_Scene->m_Registry.emplace(m_Handle, std::forward(parameters)...); - return component; - } - - template - Component& AddOrReplaceComponent(Args&&... parameters) - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - Component& component = m_Scene->m_Registry.emplace_or_replace(m_Handle, std::forward(parameters)...); - return component; - } - - template - Component& GetComponent() const - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - TR_ASSERT(HasComponent(), "Entity doesn't have the component"); - return m_Scene->m_Registry.get(m_Handle); - } - - template - void RemoveComponent() - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - TR_ASSERT(HasComponent(), "Entity doesn't have component"); - - m_Scene->m_Registry.remove(m_Handle); - } - - template - Component& TryGetComponent() const - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - return m_Scene->m_Registry.try_get(m_Handle); - } - - template - bool HasComponent() const - { - TR_ASSERT(m_Handle != entt::null, "Ivalid entity"); - - return m_Scene->m_Registry.all_of(m_Handle); - } - - // visit all the components of an entity - // the signiture of Func should be void(const entt::type_info) - template - void Visit(Entity entity, Func func) const - { - m_Scene->m_Registry.visit(entity, std::forward(func)); - } - - // base stuffs - Terran::Core::UUID const& GetID() const { return GetComponent().ID; } - TransformComponent& GetTransform() const { return GetComponent(); } - bool Valid() const { return m_Scene->m_Registry.valid(m_Handle); } - std::string const& GetName() const { return GetComponent().Name; } - - // operators - operator entt::entity() const { return m_Handle; } - bool operator!=(Entity const& other) const { return !(*this == other); } - operator uint32_t() const { return static_cast(m_Handle); } - operator bool() const { return m_Handle != entt::null; } - bool operator==(Entity const& other) const { return m_Handle == other.m_Handle && m_Scene == other.m_Scene; } - - // relationship component stuffs - std::vector& GetChildren() const { return GetComponent().Children; } - size_t GetChildCount() const { return HasComponent() ? GetComponent().Children.size() : 0; } - Terran::Core::UUID GetParentID() const { return HasComponent() ? GetComponent().Parent : Terran::Core::UUID::invalid(); } - bool HasParent() const { return HasComponent() ? m_Scene->FindEntityWithUUID(GetComponent().Parent) : false; } - - Terran::Core::UUID const& GetSceneId() const { return m_Scene->GetHandle(); } - - Entity GetChild(uint32_t index) const - { - if (!HasComponent()) - return {}; - - return m_Scene->FindEntityWithUUID(GetChildren()[index]); - } - - void SetParentID(Terran::Core::UUID const& id) - { - if (!HasComponent()) - return; - - auto& relComp = GetComponent(); - relComp.Parent = id; - } - - Entity GetParent() const - { - if (!HasComponent()) - return {}; - - return m_Scene->FindEntityWithUUID(GetParentID()); - } - - bool IsChildOf(Entity entity) const - { - if (!HasComponent()) - return false; - - if (!entity.HasComponent()) - return false; - - return GetParentID() == entity.GetID(); - } - - void SetParent(Entity parent, bool forceTransformUpdate = false) - { - if (!HasComponent()) - AddComponent(); - - if (!parent.HasComponent()) - parent.AddComponent(); - - if (IsChildOf(parent)) - return; - if (parent.IsChildOf(*this)) - return; - - if (HasParent()) - Unparent(); - - auto& relComp = GetComponent(); - relComp.Parent = parent.GetID(); - parent.GetChildren().emplace_back(GetID()); - - m_Scene->ConvertToLocalSpace(*this); - } - - void Unparent() - { - if (!HasComponent()) - return; - - Terran::Core::UUID parentID = GetComponent().Parent; - Entity parent = m_Scene->FindEntityWithUUID(parentID); - - if (!parent) - return; - - m_Scene->ConvertToWorldSpace(*this); - - auto const& it = std::ranges::find(parent.GetChildren(), GetID()); - - if (it != parent.GetChildren().end()) - parent.GetChildren().erase(it); - - SetParentID(Terran::Core::UUID({ 0 })); - - // TODO: if the relationship component is no longer necessary than remove it - } - - /*void Unparent(Entity parent, Entity child, bool removeRelationship) - { - if (!parent.HasComponent()) - return; - - if (!child.HasComponent()) - return; - - const auto& it = std::find(parent.GetChildren().begin(), parent.GetChildren().end(), child.GetID()); - - if (it != parent.GetChildren().end()) - parent.GetChildren().erase(it); - - if (removeRelationship) - child.RemoveComponent(); - else - { - RelationshipComponent& rc = child.GetComponent(); - rc.ParentID = UUID({ 0 }); - } - }*/ - - void RemoveChild(Entity child, bool removeRelationship) - { - child.Unparent(); - } - - void Reparent(Entity previousParent, Entity newParent) - { - Unparent(); - SetParent(newParent); - } - -private: - entt::entity m_Handle { entt::null }; - Scene* m_Scene = nullptr; -}; - -} -#pragma warning(pop) diff --git a/TerranEngine/src/Scene/Scene.cpp b/TerranEngine/src/Scene/Scene.cpp deleted file mode 100644 index f9ee800d..00000000 --- a/TerranEngine/src/Scene/Scene.cpp +++ /dev/null @@ -1,692 +0,0 @@ -#include "Scene.h" -#include "trpch.h" - -#include "Components.h" -#include "Entity.h" -#include "SceneManager.h" - -#include "Graphics/BatchRenderer2D.h" - -#include "Systems/SceneRenderer.h" - -#include "Scripting/ScriptEngine.h" -// #include "Scripting/ScriptCache.h" - -#include "Physics/Physics.h" -#include "Physics/PhysicsBody.h" - -#include "Project/Project.h" - -#include "Math/Math.h" - -#include "Utils/Debug/OptickProfiler.h" -#include "Utils/Debug/Profiler.h" - -#include -#include -#define GLM_ENABLE_EXPERIMENTAL -#include - -namespace TerranEngine { - -struct SceneComponent final { - Terran::Core::UUID SceneID; -}; - -namespace { - -template -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) -{ - if (!srcRegistry.all_of(srcHandle)) - return; - - dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); -} - -template<> -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry, entt::registry& dstRegistry) -{ - if (!srcRegistry.all_of(srcHandle)) - return; - - entt::entity const srcSceneEntity = srcRegistry.view().front(); - Terran::Core::UUID const& srcSceneID = srcRegistry.get(srcSceneEntity).SceneID; - Terran::Core::UUID const& srcEntityID = srcRegistry.get(srcHandle).ID; - - Terran::Core::Shared srcScriptInstance = ScriptEngine::GetScriptInstance(srcSceneID, srcEntityID); - if (!srcScriptInstance) { - TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the source scene was null"); - return; - } - dstRegistry.emplace_or_replace(dstHandle, srcRegistry.get(srcHandle)); - - entt::entity const dstSceneEntity = dstRegistry.view().front(); - Terran::Core::UUID const& dstSceneID = dstRegistry.get(dstSceneEntity).SceneID; - - Terran::Core::UUID const& dstEntityID = dstRegistry.get(dstHandle).ID; - - Terran::Core::Shared dstScriptInstance = ScriptEngine::GetScriptInstance(dstSceneID, dstEntityID); - if (!dstScriptInstance) { - TR_CORE_ERROR(TR_LOG_SCRIPT, "The script instance from the destination scene was null"); - return; - } - dstScriptInstance->CopyAllFieldsFrom(srcScriptInstance); -} - -template -void CopyComponent(entt::entity srcHandle, entt::entity dstHandle, entt::registry& srcRegistry) -{ - CopyComponent(srcHandle, dstHandle, srcRegistry, srcRegistry); -} - -static glm::mat4 CalculateTransformMatrix(TransformComponent const& transform) -{ - return glm::translate(glm::mat4(1.0f), transform.Position) * glm::toMat4(glm::quat(transform.Rotation)) * glm::scale(glm::mat4(1.0f), transform.Scale); -} - -} - -Scene::Scene() - : Scene(Terran::Core::UUID()) -{ -} - -Scene::Scene(Terran::Core::UUID const& handle) - : Asset(handle) -{ - auto const sceneEntity = m_Registry.create(); - m_Registry.emplace(sceneEntity, m_Handle); - - m_Registry.on_construct().connect<&Scene::OnScriptComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnScriptComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnRigidbody2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnRigidbody2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnBoxCollider2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnBoxCollider2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnCircleCollider2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnCircleCollider2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnCapsuleCollider2DComponentConstructed>(this); - m_Registry.on_destroy().connect<&Scene::OnCapsuleCollider2DComponentDestroyed>(this); - - m_Registry.on_construct().connect<&Scene::OnTextComponentConstructed>(this); -} - -Scene::~Scene() -{ - auto scriptableComponentView = m_Registry.view(); - - for (auto e : scriptableComponentView) { - Entity entity(e, this); - ScriptEngine::DestroyScriptInstance(entity); - } - - m_Registry.clear(); - - m_Registry.on_construct().disconnect<&Scene::OnScriptComponentConstructed>(this); - m_Registry.on_destroy().disconnect<&Scene::OnScriptComponentDestroyed>(this); - - m_Registry.on_construct().disconnect<&Scene::OnRigidbody2DComponentConstructed>(this); - m_Registry.on_destroy().disconnect<&Scene::OnRigidbody2DComponentDestroyed>(this); -} - -Entity Scene::CreateEntity(std::string const& name) -{ - return CreateEntityWithUUID(name, Terran::Core::UUID()); -} - -Entity Scene::CreateEntityWithUUID(std::string const& name, Terran::Core::UUID const& uuid) -{ - entt::entity e = m_Registry.create(); - - Entity entity(e, this); - entity.AddComponent(name.empty() ? "Entity" : name, uuid); - entity.AddComponent(); - - m_EntityMap[uuid] = e; - - SortEntities(); - return entity; -} - -Entity Scene::CreateEmptyEntity() -{ - entt::entity e = m_Registry.create(); - Entity entity(e, this); - return entity; -} - -void Scene::DestroyEntity(Entity entity, bool first) -{ - ScriptEngine::DestroyScriptInstance(entity); - - if (entity.HasComponent()) - Physics2D::DestroyPhysicsBody(entity); - - if (entity.HasComponent()) { - if (first) { - if (entity.HasParent()) - entity.GetParent().RemoveChild(entity, false); - } - - for (auto eID : entity.GetChildren()) - DestroyEntity(FindEntityWithUUID(eID), false); - } - - auto entityIt = m_EntityMap.find(entity.GetID()); - if (entityIt != m_EntityMap.end()) - m_EntityMap.erase(entityIt); - - m_Registry.destroy(entity); - - SortEntities(); -} - -void Scene::StartRuntime() -{ - if (m_IsPlaying) - return; - - m_IsPlaying = true; - - Physics2D::CreatePhysicsWorld(Project::GetPhysicsSettings()); - Physics2D::CratePhysicsBodies(this); - - auto scriptableComponentView = m_Registry.view(); - - for (auto e : scriptableComponentView) { - Entity entity(e, this); - ScriptEngine::OnStart(entity); - } -} - -void Scene::StopRuntime() -{ - if (!m_IsPlaying) - return; - - m_IsPlaying = false; - Physics2D::CleanUpPhysicsWorld(); -} - -void Scene::Update(Terran::Core::Time time) -{ - TR_PROFILE_FUNCN("Scene::Update"); - TR_PROFILE_FUNCTION(); - - UpdateTransformHierarchy(); - - Physics2D::Update(time); - - auto scriptableComponentView = m_Registry.view(); - for (auto e : scriptableComponentView) { - Entity entity(e, this); - ScriptEngine::OnUpdate(entity, time.GetDeltaTime()); - } -} - -void Scene::UpdateEditor() -{ - TR_PROFILE_FUNCTION(); - UpdateTransformHierarchy(); -} - -void Scene::OnResize(float width, float height) -{ - if (m_ViewportWidth != width || m_ViewportHeight != height) { - m_ViewportWidth = width; - m_ViewportHeight = height; - - auto cameraView = m_Registry.view(); - - for (auto e : cameraView) { - Entity entity(e, this); - auto& cameraComponent = entity.GetComponent(); - - cameraComponent.Camera.SetViewport(width, height); - } - } -} - -void Scene::OnRender(Terran::Core::Shared const& sceneRenderer) -{ - TR_PROFILE_FUNCTION(); - - if (Entity primaryCamera = GetPrimaryCamera()) { - glm::vec4 backgroundColor = primaryCamera.GetComponent().BackgroundColor; - sceneRenderer->SetScene(this); - sceneRenderer->SetClearColor(backgroundColor); - - Camera& camera = primaryCamera.GetComponent().Camera; - glm::mat4& cameraTransform = primaryCamera.GetTransform().WorldSpaceTransformMatrix; - - sceneRenderer->BeginScene(camera, cameraTransform, true); - - // submit sprites - { - auto spriteRendererView = m_Registry.view(); - for (auto e : spriteRendererView) { - Entity entity(e, this); - auto& spriteRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit circles - { - auto circleRendererView = m_Registry.view(); - for (auto e : circleRendererView) { - Entity entity(e, this); - auto& circleRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)(entity)); - } - } - - // submit text - { - auto textRendererView = m_Registry.view(); - for (auto e : textRendererView) { - Entity entity(e, this); - auto& textRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, entity); - } - } - - // submit lines - { - auto lineRendererView = m_Registry.view(); - - for (auto e : lineRendererView) { - Entity entity(e, this); - auto& lineRenderer = entity.GetComponent(); - - sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); - } - } - - sceneRenderer->EndScene(); - } -} - -void Scene::OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView) -{ - TR_PROFILE_FUNCTION(); - sceneRenderer->SetScene(this); - sceneRenderer->BeginScene(camera, cameraView, false); - - sceneRenderer->GetFramebuffer()->SetColorAttachmentValue(1, -1); - - // submit sprites - { - auto spriteRendererView = m_Registry.view(); - for (auto e : spriteRendererView) { - Entity entity(e, this); - auto& spriteRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitSprite(spriteRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit circles - { - auto circleRendererView = m_Registry.view(); - for (auto e : circleRendererView) { - Entity entity(e, this); - auto& circleRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitCircle(circleRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit text - { - auto textRendererView = m_Registry.view(); - for (auto e : textRendererView) { - Entity entity(e, this); - auto& textRenderer = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - sceneRenderer->SubmitText(textRenderer, transform.WorldSpaceTransformMatrix, (uint32_t)entity); - } - } - - // submit lines - { - auto lineRendererView = m_Registry.view(); - for (auto e : lineRendererView) { - Entity entity(e, this); - auto& lineRenderer = entity.GetComponent(); - - sceneRenderer->SubmitLine(lineRenderer, (uint32_t)entity); - } - } - - sceneRenderer->EndScene(); -} - -Entity Scene::FindEntityWithUUID(Terran::Core::UUID uuid) -{ - TR_PROFILE_FUNCTION(); - if (m_EntityMap.contains(uuid)) - return Entity(m_EntityMap.at(uuid), this); - - return {}; -} - -Entity Scene::FindEntityWithName(std::string const& name) -{ - TR_PROFILE_FUNCTION(); - auto const tagView = m_Registry.view(); - - for (auto e : tagView) { - Entity entity(e, this); - if (entity.GetName() == name) - return entity; - } - - return {}; -} - -Entity Scene::GetPrimaryCamera() -{ - auto const cameraView = m_Registry.view(); - for (auto e : cameraView) { - Entity entity(e, this); - auto& cameraComponent = entity.GetComponent(); - - if (cameraComponent.Primary) - return entity; - } - - return {}; -} - -Entity Scene::DuplicateEntity(Entity srcEntity, Entity parent) -{ - Entity dstEntity = CreateEntity(srcEntity.GetName() + " Copy"); - - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - CopyComponent(srcEntity, dstEntity, m_Registry); - - if (srcEntity.HasComponent()) { - for (int i = 0; i < srcEntity.GetChildCount(); i++) { - Entity childEntity = srcEntity.GetChild(i); - DuplicateEntity(childEntity, dstEntity); - } - - if (!parent) - parent = srcEntity.GetParent(); - - if (parent) - dstEntity.SetParent(parent); - } - - return dstEntity; -} - -Entity Scene::DuplicateEntity(Entity srcEntity) -{ - return DuplicateEntity(srcEntity, {}); -} - -Terran::Core::Shared Scene::CopyScene(Terran::Core::Shared const& srcScene) -{ - Terran::Core::Shared scene = SceneManager::CreateEmptyScene(); - - auto tagView = srcScene->GetEntitiesWith(); - - for (auto e : tagView) { - Entity srcEntity(e, srcScene.get()); - Entity dstEntity = scene->CreateEntityWithUUID(srcEntity.GetName(), srcEntity.GetID()); - } - - for (auto e : tagView) { - Entity srcEntity(e, srcScene.get()); - Entity dstEntity = scene->FindEntityWithUUID(srcEntity.GetID()); - - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - CopyComponent(srcEntity, dstEntity, srcScene->m_Registry, scene->m_Registry); - } - - scene->SortEntities(); - - return scene; -} - -void Scene::UpdateTransformHierarchy() -{ - TR_PROFILE_FUNCN("Scene::UpdateTransformHierarchy"); - TR_PROFILE_FUNCTION(); - - auto transformView = GetEntitiesWith(); - - for (auto e : transformView) { - Entity entity(e, this); - auto& transform = entity.GetTransform(); - - if (!entity.HasParent()) - UpdateEntityTransform(entity); - } -} - -void Scene::UpdateEntityTransform(Entity entity) -{ - TransformComponent& tc = entity.GetComponent(); - - if (tc.IsDirty) { - if (Entity parent = entity.GetParent()) { - glm::mat4 parentTransform = parent.GetTransform().WorldSpaceTransformMatrix; - tc.WorldSpaceTransformMatrix = parentTransform * CalculateTransformMatrix(tc); - tc.LocalSpaceTransformMatrix = glm::inverse(parentTransform) * tc.WorldSpaceTransformMatrix; - } else { - tc.WorldSpaceTransformMatrix = CalculateTransformMatrix(tc); - tc.LocalSpaceTransformMatrix = tc.WorldSpaceTransformMatrix; - } - - glm::quat rotation = tc.Rotation; - - tc.Forward = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 0.0f, 1.0f))); - tc.Up = glm::normalize(glm::rotate(rotation, glm::vec3(0.0f, 1.0f, 0.0f))); - tc.Right = glm::normalize(glm::rotate(rotation, glm::vec3(1.0f, 0.0f, 0.0f))); - } - - for (size_t i = 0; i < entity.GetChildCount(); i++) { - Entity currEntity = entity.GetChild(static_cast(i)); - - if (tc.IsDirty) - currEntity.GetTransform().IsDirty = true; - - UpdateEntityTransform(currEntity); - } - - tc.IsDirty = false; -} - -void Scene::ConvertToLocalSpace(Entity entity) -{ - auto& tc = entity.GetComponent(); - - if (!entity.HasParent()) - return; - - if (tc.IsDirty) - UpdateEntityTransform(entity); - - Entity parent = entity.GetParent(); - auto& parentTransform = parent.GetTransform(); - - // NOTE: have to calculate it because at this point the local space - // and world space transform matrices are equal - glm::mat4 parentWorldMatrix = parentTransform.WorldSpaceTransformMatrix; - glm::mat4 localMat = glm::inverse(parentWorldMatrix) * tc.WorldSpaceTransformMatrix; - - Math::Decompose(localMat, tc.Position, tc.Rotation, tc.Scale); - - tc.IsDirty = true; -} - -void Scene::ConvertToWorldSpace(Entity entity) -{ - auto& tc = entity.GetComponent(); - - if (!entity.HasParent()) - return; - - if (tc.IsDirty) - UpdateEntityTransform(entity); - - Math::Decompose(tc.WorldSpaceTransformMatrix, tc.Position, tc.Rotation, tc.Scale); - - tc.IsDirty = true; -} - -void Scene::SortEntities() -{ - m_Registry.sort([](entt::entity const& lEntity, entt::entity const& rEntity) { return lEntity < rEntity; }); -} - -void Scene::OnScriptComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - ScriptEngine::CreateScriptInstance(entity); - - if (m_IsPlaying) - ScriptEngine::OnStart(entity); -} - -void Scene::OnScriptComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - ScriptEngine::DestroyScriptInstance(entity); -} - -void Scene::OnRigidbody2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Terran::Core::Shared physicsBody = Physics2D::CreatePhysicsBody(entity); - physicsBody->AttachColliders(); - } -} - -void Scene::OnRigidbody2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Physics2D::DestroyPhysicsBody(entity); - } -} - -void Scene::OnBoxCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - if (!entity.HasComponent()) - entity.AddComponent(); - - if (!m_IsPlaying) - return; - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->AddCollider(entity); -} -void Scene::OnBoxCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); - auto& bcComponent = entity.GetComponent(); - - if (physicsBody) - physicsBody->RemoveCollider(bcComponent.ColliderIndex); - } -} - -void Scene::OnCircleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - if (!entity.HasComponent()) - entity.AddComponent(); - - if (!m_IsPlaying) - return; - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->AddCollider(entity); -} -void Scene::OnCircleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - auto& ccComponent = entity.GetComponent(); - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->RemoveCollider(ccComponent.ColliderIndex); - } -} - -void Scene::OnCapsuleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - if (!entity.HasComponent()) - entity.AddComponent(); - - if (!m_IsPlaying) - return; - - if (Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity)) - physicsBody->AddCollider(entity); -} -void Scene::OnCapsuleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ - if (m_IsPlaying) { - Entity entity(entityHandle, this); - Terran::Core::Shared physicsBody = Physics2D::GetPhysicsBody(entity); - auto& ccComponent = entity.GetComponent(); - - if (physicsBody) - physicsBody->RemoveCollider(ccComponent.ColliderIndex); - } -} - -void Scene::OnTextComponentConstructed(entt::registry& registry, entt::entity entityHandle) -{ - Entity entity(entityHandle, this); - auto& trc = entity.GetComponent(); - if (!trc.FontAtlas) - trc.FontAtlas = Font::DefaultFont; -} - -void Scene::OnTextComponentDestroyed(entt::registry& registry, entt::entity entityHandle) -{ -} - -} diff --git a/TerranEngine/src/Scene/Scene.h b/TerranEngine/src/Scene/Scene.h deleted file mode 100644 index cafe6996..00000000 --- a/TerranEngine/src/Scene/Scene.h +++ /dev/null @@ -1,135 +0,0 @@ -#pragma once - -#include "LibCore/Base.h" -#include "LibCore/Time.h" -#include "LibCore/UUID.h" - -#include "Asset/Asset.h" - -#include "Graphics/Camera.h" - -#pragma warning(push) -#pragma warning(disable : 26439) -#include -#pragma warning(pop) - -#include -#include -#include -#include - -namespace TerranEngine { - -class Entity; -class SceneRenderer; - -class Scene final : public Asset { -public: - Scene(); - Scene(Terran::Core::UUID const& handle); - ~Scene() override; - - ASSET_CLASS_TYPE(Scene) - - Entity CreateEntity(std::string const& name = std::string()); - Entity CreateEntityWithUUID(std::string const& name, Terran::Core::UUID const& uuid); - Entity CreateEmptyEntity(); - - void DestroyEntity(Entity entity, bool first); - - void StartRuntime(); - void StopRuntime(); - - void Update(Terran::Core::Time time); - void UpdateEditor(); - void OnResize(float width, float height); - - void OnRender(Terran::Core::Shared const& sceneRenderer); - void OnRenderEditor(Terran::Core::Shared const& sceneRenderer, Camera& camera, glm::mat4& cameraView); - - Entity FindEntityWithUUID(Terran::Core::UUID uuid); - Entity FindEntityWithName(std::string const& name); - - template - auto GetEntitiesWith(entt::exclude_t exclude = {}) { return m_Registry.view(exclude); } - - std::unordered_map& GetEntityMap() { return m_EntityMap; } - - // template - // std::vector Filter(Predicate&& predicate) - // { - // std::vector entities; - // auto view = m_Registry.view...>(); - // entities.reserve(view.size()); - // - // for (auto e : view) - // { - // if (predicate(view.template get>(e)...)) - // entities.push_back({ e, this }); - // } - // - // return entities; - // } - - Entity GetPrimaryCamera(); - - Entity DuplicateEntity(Entity srcEntity, Entity parent); - Entity DuplicateEntity(Entity srcEntity); - - static Terran::Core::Shared CopyScene(Terran::Core::Shared const& srcScene); - - bool IsPlaying() const { return m_IsPlaying; } - - Scene* GetRaw() { return this; } - - void UpdateTransformHierarchy(); - void UpdateEntityTransform(Entity entity); - - void ConvertToLocalSpace(Entity entity); - void ConvertToWorldSpace(Entity entity); - - void SortEntities(); - glm::vec2 const& GetViewportPosition() const { return m_ViewportPosition; } - void SetViewportPosition(glm::vec2 const& viewportPosition) { m_ViewportPosition = viewportPosition; } - - float GetViewportWidth() const { return m_ViewportWidth; } - float GetViewportHeight() const { return m_ViewportHeight; } - -private: - // scripting components - void OnScriptComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnScriptComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - // physics components - void OnRigidbody2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnRigidbody2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - void OnBoxCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnBoxCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - void OnCircleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnCircleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - void OnCapsuleCollider2DComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnCapsuleCollider2DComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - - // text component - void OnTextComponentConstructed(entt::registry& registry, entt::entity entityHandle); - void OnTextComponentDestroyed(entt::registry& registry, entt::entity entityHandle); - -private: - bool m_IsPlaying = false; - - std::unordered_map m_EntityMap; - - entt::registry m_Registry; - glm::vec2 m_ViewportPosition = { 0.0f, 0.0f }; - float m_ViewportWidth = 1080, m_ViewportHeight = 720; - - friend class SceneRenderer; - friend class Entity; - friend class SceneSerializer; - friend class SceneAssetLoader; -}; - -} diff --git a/TerranEngine/src/Scene/SceneManager.cpp b/TerranEngine/src/Scene/SceneManager.cpp deleted file mode 100644 index d61d161d..00000000 --- a/TerranEngine/src/Scene/SceneManager.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "trpch.h" -#include "SceneManager.h" - -#include "Core/Application.h" - -#include "Events/SceneEvent.h" - -namespace TerranEngine { - -Terran::Core::Shared SceneManager::s_CurrentScene; -std::unordered_map> SceneManager::s_ActiveScenes; - -Terran::Core::Shared SceneManager::CreateEmptyScene() -{ - // TODO: create memory asset??? - Terran::Core::Shared scene = Terran::Core::CreateShared(); - s_ActiveScenes[scene->GetHandle()] = scene; - return scene; -} - -void SceneManager::RemoveScene(const Terran::Core::UUID& id) -{ - if (s_ActiveScenes.contains(id)) - s_ActiveScenes.erase(id); - - if (s_CurrentScene->GetHandle() == id) - s_CurrentScene = nullptr; -} - -Terran::Core::Shared SceneManager::GetScene(const Terran::Core::UUID& id) -{ - if (s_ActiveScenes.contains(id)) - return s_ActiveScenes.at(id); - - return nullptr; -} - -void SceneManager::SetCurrentScene(Terran::Core::Shared newScene) -{ - Terran::Core::UUID id({ 0 }); - if (s_CurrentScene) - id = s_CurrentScene->GetHandle(); - - SceneTransitionEvent sceneTransitionEvent(s_CurrentScene, newScene); - Application::Get()->DispatchEvent(sceneTransitionEvent); - - s_CurrentScene = newScene; - s_ActiveScenes[newScene->GetHandle()] = newScene; - - if (!id) - return; - - if (s_ActiveScenes.contains(id)) { - if (s_ActiveScenes[id].use_count() > 1) - return; - - s_ActiveScenes.erase(id); - } -} - -} diff --git a/TerranEngine/src/Scene/SceneManager.h b/TerranEngine/src/Scene/SceneManager.h deleted file mode 100644 index d7e391ea..00000000 --- a/TerranEngine/src/Scene/SceneManager.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "LibCore/Base.h" -#include "LibCore/UUID.h" -#include "Scene.h" - -#include - -namespace TerranEngine { - -class SceneManager final { -public: - static Terran::Core::Shared CreateEmptyScene(); - static void RemoveScene(Terran::Core::UUID const& id); - - static Terran::Core::Shared GetScene(Terran::Core::UUID const& id); - - static Terran::Core::Shared const& GetCurrentScene() { return s_CurrentScene; } - static void SetCurrentScene(Terran::Core::Shared newScene); - - static std::unordered_map>& GetActiveScenes() { return s_ActiveScenes; } - -private: - static std::unordered_map> s_ActiveScenes; - static Terran::Core::Shared s_CurrentScene; -}; - -} diff --git a/TerranEngine/src/Scene/SceneSerializer.cpp b/TerranEngine/src/Scene/SceneSerializer.cpp deleted file mode 100644 index d0d9bdf5..00000000 --- a/TerranEngine/src/Scene/SceneSerializer.cpp +++ /dev/null @@ -1,576 +0,0 @@ -#include "trpch.h" - -#include "Entity.h" -#include "SceneSerializer.h" - -#include "Scripting/ScriptEngine.h" - -#include "Utils/SerializerUtils.h" - -#include - -#include -#define GLM_ENABLE_EXPERIMENTAL -#include - -namespace TerranEngine { - -char const* SceneSerializer::SceneFilter = "Terran Scene\0*.terran\0"; -static char const* SerializerVersion = "yml1.0"; - -SceneSerializer::SceneSerializer(Terran::Core::Shared const& scene) - : m_Scene(scene) -{ -} - -#define WRITE_SCRIPT_FIELD(FieldType, Type) \ - case ScriptFieldType::FieldType: \ - out << YAML::Key << field.Name << YAML::Value << scriptInstance->GetFieldValue(fieldID); \ - break - -#define WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptInstance->GetFieldArrayValue(array, i); \ - out << value; \ - } break - -// TODO: n dimensional arrays maybe someday in the future? -// static void SerializeScriptArray(YAML::Emitter& out, Shared scriptInstance, const ScriptArray& array, const ScriptField& field, int32_t* indices, int dimension = 0) -//{ -// int32_t length = scriptInstance->GetFieldArrayLength(array, dimension); -// if (dimension == array.Rank - 1) -// { -// out << YAML::BeginSeq; -// for (int32_t i = 0; i < length; i++) -// { -// indices[dimension] = i; -// switch (field.Type) -// { -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); -// // NOTE: maybe wchar_t? -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); -// WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, UUID); -// default: TR_ASSERT(false, "Unsupported field type"); break; -// } -// } -// out << YAML::EndSeq; -// } -// else -// { -// out << YAML::BeginSeq; -// for (int32_t i = 0; i < length; i++) -// { -// indices[dimension] = i; -// SerializeScriptArray(out, scriptInstance, array, field, indices, dimension + 1); -// } -// out << YAML::EndSeq; -// } -//} - -static void SerializeScriptFields(YAML::Emitter& out, Entity entity) -{ - ScriptComponent& sc = entity.GetComponent(); - Terran::Core::Shared scriptInstance = ScriptEngine::GetScriptInstance(entity); - - for (auto& fieldID : sc.FieldHandles) { - ScriptField field = scriptInstance->GetScriptField(fieldID); - - if (field.IsArray) { - ScriptArray array = scriptInstance->GetScriptArray(fieldID); - if (array.Rank > 1) - continue; - - out << YAML::BeginMap; - out << YAML::Key << field.Name << YAML::Value << YAML::Flow; - out << YAML::BeginSeq; - - int32_t length = scriptInstance->GetFieldArrayLength(array, 0); - for (int32_t i = 0; i < length; i++) { - switch (field.Type) { - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); - // NOTE: maybe wchar_t? - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, std::byte); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); - WRITE_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Unsupported field type"); - break; - } - } - out << YAML::EndSeq; - out << YAML::EndMap; - } else { - out << YAML::BeginMap; - switch (field.Type) { - WRITE_SCRIPT_FIELD(Bool, bool); - // TODO: wchar - WRITE_SCRIPT_FIELD(Char, char); - WRITE_SCRIPT_FIELD(Int8, int8_t); - WRITE_SCRIPT_FIELD(Int16, int16_t); - WRITE_SCRIPT_FIELD(Int32, int32_t); - WRITE_SCRIPT_FIELD(Int64, int64_t); - WRITE_SCRIPT_FIELD(UInt8, std::byte); - WRITE_SCRIPT_FIELD(UInt16, uint16_t); - WRITE_SCRIPT_FIELD(UInt32, uint32_t); - WRITE_SCRIPT_FIELD(UInt64, uint64_t); - WRITE_SCRIPT_FIELD(Float, float); - WRITE_SCRIPT_FIELD(Double, double); - WRITE_SCRIPT_FIELD(String, std::string); - WRITE_SCRIPT_FIELD(Vector2, glm::vec2); - WRITE_SCRIPT_FIELD(Vector3, glm::vec3); - WRITE_SCRIPT_FIELD(Color, glm::vec4); - WRITE_SCRIPT_FIELD(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Unsupported field type"); - break; - } - out << YAML::EndMap; - } - } -} - -#define BEGIN_COMPONENT_MAP(componentKey) \ - out << YAML::Key << componentKey; \ - out << YAML::BeginMap - -#define END_COMPONENT_MAP() \ - out << YAML::EndMap - -#define WRITE_COMPONENT_PROPERY(propertyName, property) \ - out << YAML::Key << propertyName << YAML::Value << property - -static void SerializeEntity(YAML::Emitter& out, Entity entity) -{ - // TODO: convert to a verify assert - TR_ASSERT(entity.HasComponent(), "Can't serialize an entity that doesn't have a tag component"); - out << YAML::BeginMap; - - WRITE_COMPONENT_PROPERY("Entity", entity.GetID()); - - BEGIN_COMPONENT_MAP("TagComponent"); - auto& tagComponent = entity.GetComponent(); - WRITE_COMPONENT_PROPERY("Tag", tagComponent.Name); - END_COMPONENT_MAP(); - - if (entity.HasComponent()) { - auto& transformComponent = entity.GetTransform(); - - BEGIN_COMPONENT_MAP("TransformComponent"); - WRITE_COMPONENT_PROPERY("Position", transformComponent.Position); - WRITE_COMPONENT_PROPERY("Scale", transformComponent.Scale); - WRITE_COMPONENT_PROPERY("Rotation", transformComponent.Rotation); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& cameraComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CameraComponent"); - WRITE_COMPONENT_PROPERY("Camera", YAML::BeginMap); - WRITE_COMPONENT_PROPERY("Size", cameraComponent.Camera.GetOrthographicSize()); - WRITE_COMPONENT_PROPERY("Near", cameraComponent.Camera.GetOrthographicNear()); - WRITE_COMPONENT_PROPERY("Far", cameraComponent.Camera.GetOrthographicFar()); - out << YAML::EndMap; - - WRITE_COMPONENT_PROPERY("Primary", cameraComponent.Primary); - WRITE_COMPONENT_PROPERY("ClearColor", cameraComponent.BackgroundColor); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& spriteRendererComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("SpriteRendererComponent"); - WRITE_COMPONENT_PROPERY("Color", spriteRendererComponent.Color); - WRITE_COMPONENT_PROPERY("Texture", spriteRendererComponent.TextureHandle); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& circleRendererComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CircleRendererComponent"); - WRITE_COMPONENT_PROPERY("Color", circleRendererComponent.Color); - WRITE_COMPONENT_PROPERY("Thickness", circleRendererComponent.Thickness); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& textRendererComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("TextRendererCompoent"); - WRITE_COMPONENT_PROPERY("Color", textRendererComponent.TextColor); - WRITE_COMPONENT_PROPERY("Text", textRendererComponent.Text); - // TODO: save font handle - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& relationshipComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("RelationshipComponent"); - WRITE_COMPONENT_PROPERY("Children", YAML::BeginSeq); - for (auto child : relationshipComponent.Children) - out << child; - out << YAML::EndSeq; - WRITE_COMPONENT_PROPERY("Parent", relationshipComponent.Parent); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& scriptComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("ScriptComponent"); - WRITE_COMPONENT_PROPERY("ModuleName", scriptComponent.ModuleName); - if (!scriptComponent.FieldHandles.empty()) { - out << YAML::Key << "Fields" << YAML::Value; - out << YAML::BeginSeq; - SerializeScriptFields(out, entity); - out << YAML::EndSeq; - } - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& rigidbodyComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("Rigidbody2DComponent"); - WRITE_COMPONENT_PROPERY("BodyType", PhysicsBodyTypeToString(rigidbodyComponent.BodyType).data()); - WRITE_COMPONENT_PROPERY("FixedRotation", rigidbodyComponent.FixedRotation); - WRITE_COMPONENT_PROPERY("SleepState", PhysicsBodySleepStateToString(rigidbodyComponent.SleepState).data()); - WRITE_COMPONENT_PROPERY("GravityScale", rigidbodyComponent.GravityScale); - WRITE_COMPONENT_PROPERY("Material", rigidbodyComponent.PhysicsMaterialHandle); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& boxCollider2DComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("BoxCollider2DComponent"); - WRITE_COMPONENT_PROPERY("Offset", boxCollider2DComponent.Offset); - WRITE_COMPONENT_PROPERY("Size", boxCollider2DComponent.Size); - WRITE_COMPONENT_PROPERY("Sensor", boxCollider2DComponent.Sensor); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& circleCollider2DComponent = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CircleCollider2DComponent"); - WRITE_COMPONENT_PROPERY("Offset", circleCollider2DComponent.Offset); - WRITE_COMPONENT_PROPERY("Radius", circleCollider2DComponent.Radius); - WRITE_COMPONENT_PROPERY("Sensor", circleCollider2DComponent.Sensor); - END_COMPONENT_MAP(); - } - - if (entity.HasComponent()) { - auto& capsuleColliderComponenet = entity.GetComponent(); - - BEGIN_COMPONENT_MAP("CapsuleCollider2DComponent"); - WRITE_COMPONENT_PROPERY("Offset", capsuleColliderComponenet.Offset); - WRITE_COMPONENT_PROPERY("Size", capsuleColliderComponenet.Size); - WRITE_COMPONENT_PROPERY("Sensor", capsuleColliderComponenet.Sensor); - END_COMPONENT_MAP(); - } - - out << YAML::EndMap; -} - -void SceneSerializer::SerializeEditor(std::filesystem::path const& scenePath) -{ - YAML::Emitter out; - - out << YAML::BeginMap; - - out << YAML::Key << "SerializerVersion" << YAML::Value << SerializerVersion; - out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq; - - auto const tagComponentView = m_Scene->GetEntitiesWith(); - for (auto e : tagComponentView) { - Entity entity(e, m_Scene.get()); - SerializeEntity(out, entity); - } - - out << YAML::EndSeq; - out << YAML::EndMap; - - std::ofstream ofs(scenePath); - ofs << out.c_str(); -} - -#define READ_SCRIPT_FIELD(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptFieldNode.as(); \ - scriptInstance->SetFieldValue(fieldID, value); \ - } break - -#define READ_SCRIPT_FIELD_ARRAY_ELEMENT(FieldType, Type) \ - case ScriptFieldType::FieldType: { \ - Type value = scriptFieldNode[i].as(); \ - scriptInstance->SetFieldArrayValue(array, value, i); \ - } break - -static void DeserializeScriptFields(Terran::Core::Shared scriptInstance, ScriptComponent& scriptComponent, const YAML::Node& scriptFieldsNode) -{ - for (auto const& fieldID : scriptComponent.FieldHandles) { - ScriptField scriptField = scriptInstance->GetScriptField(fieldID); - YAML::Node scriptFieldNode; - bool valid = false; - - for (auto field : scriptFieldsNode) { - if (field[scriptField.Name]) { - scriptFieldNode = field[scriptField.Name]; - valid = true; - break; - } - } - - if (!valid) - continue; - - if (scriptField.IsArray) { - ScriptArray array = scriptInstance->GetScriptArray(fieldID); - if (array.Rank > 1) - continue; - - // TODO: n dimensional arrays someday in the future? - int32_t length = scriptInstance->GetFieldArrayLength(array); - for (int32_t i = 0; i < length && i < scriptFieldNode.size(); i++) { - switch (scriptField.Type) { - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Bool, bool); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Char, char); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int64, int64_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int32, int32_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int16, int16_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Int8, int8_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt64, uint64_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt32, uint32_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt16, uint16_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(UInt8, uint8_t); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Float, float); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Double, double); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(String, std::string); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector2, glm::vec2); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Vector3, glm::vec3); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Color, glm::vec4); - READ_SCRIPT_FIELD_ARRAY_ELEMENT(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Invalid script type"); - } - } - } else { - switch (scriptField.Type) { - READ_SCRIPT_FIELD(Bool, bool); - READ_SCRIPT_FIELD(Char, char); - READ_SCRIPT_FIELD(Int64, int64_t); - READ_SCRIPT_FIELD(Int32, int32_t); - READ_SCRIPT_FIELD(Int16, int16_t); - READ_SCRIPT_FIELD(Int8, int8_t); - READ_SCRIPT_FIELD(UInt64, uint64_t); - READ_SCRIPT_FIELD(UInt32, uint32_t); - READ_SCRIPT_FIELD(UInt16, uint16_t); - READ_SCRIPT_FIELD(UInt8, uint8_t); - READ_SCRIPT_FIELD(Float, float); - READ_SCRIPT_FIELD(Double, double); - READ_SCRIPT_FIELD(String, std::string); - READ_SCRIPT_FIELD(Vector2, glm::vec2); - READ_SCRIPT_FIELD(Vector3, glm::vec3); - READ_SCRIPT_FIELD(Color, glm::vec4); - READ_SCRIPT_FIELD(Entity, Terran::Core::UUID); - default: - TR_ASSERT(false, "Invalid script type"); - } - } - } -} - -static YAML::Node FindEntity(YAML::Node scene, Terran::Core::UUID const& entityID) -{ - for (auto entity : scene) { - TR_CORE_TRACE(TR_LOG_CORE, entity); - Terran::Core::UUID id = entity["Entity"].as(); - if (id == entityID) - return entity; - } - - return {}; -} - -static Entity DeserializeEntity(YAML::Node data, YAML::Node scene, Terran::Core::Shared deserializedScene) -{ - try { - Terran::Core::UUID id = data["Entity"].as(); - if (!id) { - TR_ASSERT(false, "Invalid id"); - return {}; - } - - Entity entity = deserializedScene->FindEntityWithUUID(id); - if (entity) - return entity; - - auto tagComponent = data["TagComponent"]; - if (!tagComponent) { - TR_ASSERT(false, "Invalid tag component"); - return {}; - } - - std::string name = tagComponent["Tag"].as(); - Entity deserializedEntity = deserializedScene->CreateEntityWithUUID(name, id); - - auto transformComponent = data["TransformComponent"]; - if (transformComponent) { - auto& tc = deserializedEntity.GetTransform(); - tc.Position = transformComponent["Position"].as(glm::vec3(0.0f, 0.0f, 0.0f)); - tc.Rotation = transformComponent["Rotation"].as(glm::vec3(0.0f, 0.0f, 0.0f)); - tc.Scale = transformComponent["Scale"].as(glm::vec3(1.0f, 1.0f, 1.0f)); - } - - auto cameraComponent = data["CameraComponent"]; - if (cameraComponent) { - auto& cc = deserializedEntity.AddComponent(); - auto camera = cameraComponent["Camera"]; - cc.Camera.SetOrthographicSize(camera["Size"].as(10.0f)); - cc.Camera.SetOrthographicNear(camera["Near"].as(-10.0f)); - cc.Camera.SetOrthographicFar(camera["Far"].as(10.0f)); - - cc.Primary = cameraComponent["Primary"].as(false); - cc.BackgroundColor = cameraComponent["ClearColor"].as(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); - } - - auto spriteRendererComponent = data["SpriteRendererComponent"]; - if (spriteRendererComponent) { - auto& src = deserializedEntity.AddComponent(); - src.Color = spriteRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - src.TextureHandle = spriteRendererComponent["Texture"].as(Terran::Core::UUID::Invalid()); - } - - auto circleRendererComponent = data["CircleRendererComponent"]; - if (circleRendererComponent) { - auto& crc = deserializedEntity.AddComponent(); - crc.Color = circleRendererComponent["Color"].as(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - crc.Thickness = circleRendererComponent["Thickness"].as(1.0f); - } - - auto relationshipComponent = data["RelationshipComponent"]; - if (relationshipComponent) { - auto& rc = deserializedEntity.AddComponent(); - for (auto childID : relationshipComponent["Children"]) { - Terran::Core::UUID deserializedChildID = childID.as(); - Entity child = deserializedScene->FindEntityWithUUID(deserializedChildID); - if (!child) { - YAML::Node childNode = FindEntity(scene, deserializedChildID); - child = DeserializeEntity(childNode, scene, deserializedScene); - } - - if (child) - child.SetParent(deserializedEntity, true); - } - } - - auto scriptComponent = data["ScriptComponent"]; - if (scriptComponent) { - auto& sc = deserializedEntity.AddComponent(); - sc.ModuleName = scriptComponent["ModuleName"].as(); - - Terran::Core::Shared scriptInstance = ScriptEngine::CreateScriptInstance(deserializedEntity); - auto scriptFields = scriptComponent["Fields"]; - if (scriptFields) - DeserializeScriptFields(scriptInstance, sc, scriptFields); - } - - auto boxColliderComponent = data["BoxCollider2DComponent"]; - if (boxColliderComponent) { - auto& bcc = deserializedEntity.AddComponent(); - bcc.Offset = boxColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - bcc.Size = boxColliderComponent["Size"].as(glm::vec2(1.0f, 1.0f)); - bcc.Sensor = boxColliderComponent["Sensor"].as(false); - } - - auto circleColliderComponent = data["CircleCollider2DComponent"]; - if (circleRendererComponent) { - auto& ccc = deserializedEntity.AddComponent(); - ccc.Offset = circleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - ccc.Radius = circleColliderComponent["Radius"].as(0.5f); - ccc.Sensor = circleColliderComponent["Sensor"].as(false); - } - - auto rigidbodyComponent = data["Rigidbody2DComponent"]; - if (rigidbodyComponent) { - auto& rbc = deserializedEntity.AddComponent(); - rbc.BodyType = PhysicsBodyTypeFromString(rigidbodyComponent["BodyType"].as()); - rbc.FixedRotation = rigidbodyComponent["FixedRotation"].as(false); - rbc.SleepState = PhysicsBodySleepStateFromString(rigidbodyComponent["SleepState"].as()); - rbc.GravityScale = rigidbodyComponent["GravityScale"].as(1.0f); - - if (rigidbodyComponent["Material"]) - rbc.PhysicsMaterialHandle = rigidbodyComponent["Material"].as(Terran::Core::UUID::Invalid()); - } - - auto capsuleColliderComponent = data["CapsuleCollider2DComponent"]; - if (capsuleColliderComponent) { - auto& ccc = deserializedEntity.AddComponent(); - ccc.Offset = capsuleColliderComponent["Offset"].as(glm::vec2(0.0f, 0.0f)); - ccc.Size = capsuleColliderComponent["Size"].as(glm::vec2(0.5f, 1.0f)); - ccc.Sensor = capsuleColliderComponent["Sensor"].as(false); - } - - return deserializedEntity; - } catch (YAML::InvalidNode const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return Entity(); - } -} - -Result SceneSerializer::DeserializeEditor(std::filesystem::path const& scenePath) -{ - YAML::Node data; - try { - data = YAML::LoadFile(scenePath.string()); - } catch (YAML::ParserException const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return Result::PARSE_ERROR; - } catch (YAML::BadFile const& ex) { - TR_CORE_ERROR(TR_LOG_ASSET, ex.what()); - return Result::NOT_FOUND; - } - - auto entities = data["Entities"]; - if (entities) { - for (auto entity : entities) { - if (!DeserializeEntity(entity, entities, m_Scene)) - return Result::PARSE_ERROR; - } - } - - return Result::OK; -} - -} diff --git a/TerranEngine/src/Scene/SceneSerializer.h b/TerranEngine/src/Scene/SceneSerializer.h deleted file mode 100644 index 4a570052..00000000 --- a/TerranEngine/src/Scene/SceneSerializer.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "Scene.h" - -#include "Core/Result.h" -#include "LibCore/Base.h" - -#include - -namespace TerranEngine { - -class SceneSerializer final { -public: - SceneSerializer() = default; - SceneSerializer(Terran::Core::Shared const& scene); - - void SerializeEditor(std::filesystem::path const& scenePath); - Result DeserializeEditor(std::filesystem::path const& scenePath); - -public: - static char const* SceneFilter; - -private: - Terran::Core::Shared m_Scene; -}; - -} diff --git a/TerranEngine/src/Scene/Systems/SceneRenderer.cpp b/TerranEngine/src/Scene/Systems/SceneRenderer.cpp deleted file mode 100644 index 035ee581..00000000 --- a/TerranEngine/src/Scene/Systems/SceneRenderer.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "SceneRenderer.h" -#include "trpch.h" - -#include "Scene/Entity.h" - -#include "Core/Input.h" - -#include "Graphics/BatchRenderer2D.h" -#include "Graphics/Renderer.h" - -#include "Asset/AssetManager.h" - -#include "Math/Math.h" - -#include - -namespace TerranEngine { - -SceneRenderer::SceneRenderer(FramebufferParameters const& params) - : m_Scene(nullptr) -{ - m_Framebuffer = Terran::Core::CreateShared(params); -} - -void SceneRenderer::SetScene(Scene* scene) -{ - m_Scene = scene; -} - -void SceneRenderer::BeginScene(Camera& camera, glm::mat4 const& cameraTransform, bool inverseTransform) -{ - m_Framebuffer->Bind(); - - Renderer::SetClearColor(m_ClearColor.r, m_ClearColor.g, m_ClearColor.b, 1.0f); - Renderer::Clear(); - - BatchRenderer2D::BeginFrame(camera, cameraTransform, inverseTransform); - m_BegunScene = true; - - /* TODO: better sorting - * also add circle sorting - */ - - m_Scene->m_Registry.sort([](auto const& lEntity, auto const& rEntity) { return lEntity.ZIndex < rEntity.ZIndex; }); -} - -void SceneRenderer::SubmitSprite(SpriteRendererComponent const& spriteRenderer, glm::mat4& transform, int entityID) -{ - // TODO: frustum culling - Terran::Core::Shared texture = AssetManager::GetAssetByHandle(spriteRenderer.TextureHandle); - BatchRenderer2D::AddQuad(transform, spriteRenderer.Color, texture, entityID); -} - -void SceneRenderer::SubmitCircle(CircleRendererComponent const& circleRenderer, glm::mat4& transform, int entityID) -{ - // TODO: frustum culling - BatchRenderer2D::AddCircle(transform, circleRenderer.Color, circleRenderer.Thickness, entityID); -} - -void SceneRenderer::SubmitLine(LineRendererComponent const& lineRenderer, int entityID) -{ - BatchRenderer2D::AddLine(lineRenderer.StartPoint, lineRenderer.EndPoint, lineRenderer.Color, lineRenderer.Thickness, entityID); -} - -void SceneRenderer::SubmitText(TextRendererComponent const& textRenderer, glm::mat4& transform, int entityID) -{ - BatchRenderer2D::AddString(transform, textRenderer.Text, textRenderer.TextColor, textRenderer.FontAtlas, - textRenderer.LineSpacing, textRenderer.LineWidth, entityID); -} - -void SceneRenderer::EndScene() -{ - // TODO: draw grid - if (m_ShowColliders) - SubmitColliderBounds(); - - TR_ASSERT(m_BegunScene, "BeginScene has to be called before EndScene!"); - - BatchRenderer2D::EndFrame(); - m_Framebuffer->Unbind(); - - m_BegunScene = false; -} - -void SceneRenderer::OnResize(uint32_t width, uint32_t height) -{ - if (m_Framebuffer->GetWidth() != width || m_Framebuffer->GetHeight() != height) - m_Framebuffer->Resize(width, height); -} - -void SceneRenderer::SubmitColliderBounds() -{ - // submit box collider bounds - { - auto boxColliderView = m_Scene->GetEntitiesWith(); - for (auto e : boxColliderView) { - Entity entity(e, m_Scene); - - BoxCollider2DComponent& boxCollider = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - constexpr glm::vec4 color = { 0.0f, 1.0f, 0.0f, 1.0f }; - constexpr float thickness = 0.05f; - - glm::vec3 size = { boxCollider.Size.x, boxCollider.Size.y, 1.0f }; - - glm::vec3 position = { boxCollider.Offset.x, boxCollider.Offset.y, 1.0f }; - - glm::mat4 worldTransformMatrix = transform.WorldSpaceTransformMatrix; - glm::mat4 transformMatrix = worldTransformMatrix * glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), size); - - /*const glm::vec3 size = { transform.Scale.x * boxCollider.Size.x, transform.Scale.y * boxCollider.Size.y, 1.0f }; - - const glm::vec3 postition = { transform.Position.x + boxCollider.Offset.x, transform.Position.y + boxCollider.Offset.y, 1.0f }; - - glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), postition) * - glm::rotate(glm::mat4(1.0f), transform.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * - glm::scale(glm::mat4(1.0f), size);*/ - - BatchRenderer2D::AddDebugRect(transformMatrix, color); - } - } - - // submit circle collider bounds - { - auto circleColliderView = m_Scene->GetEntitiesWith(); - - for (auto e : circleColliderView) { - Entity entity(e, m_Scene); - - auto& circleCollider = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - constexpr glm::vec4 color = { 0.0f, 1.0f, 0.0f, 1.0f }; - constexpr float thickness = 0.02f; - - glm::vec3 position, rotation, scale; - - Math::Decompose(transform.WorldSpaceTransformMatrix, position, rotation, scale); - - // choose which component of the scale to apply - float scalingFactor = scale.x > scale.y ? scale.x : scale.y; - - glm::vec3 colliderSize = scalingFactor * glm::vec3(circleCollider.Radius * 2.0f); - glm::vec3 colliderPosition = { position.x + circleCollider.Offset.x, - position.y + circleCollider.Offset.y, 1.0f }; - - glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), colliderPosition) * glm::rotate(glm::mat4(1.0f), rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * glm::scale(glm::mat4(1.0f), colliderSize); - - BatchRenderer2D::AddCircle(transformMatrix, color, thickness, -1); - } - } - - // submit capsule collider bounds - { - auto capsuleColliderView = m_Scene->GetEntitiesWith(); - - for (auto e : capsuleColliderView) { - Entity entity(e, m_Scene); - - auto& capsuleCollider = entity.GetComponent(); - auto& transform = entity.GetTransform(); - - constexpr glm::vec4 color = { 0.0f, 1.0f, 0.0f, 1.0f }; - constexpr float thickness = 0.02f; - - float ySize = capsuleCollider.Size.x > capsuleCollider.Size.y ? capsuleCollider.Size.x : capsuleCollider.Size.y; - glm::vec3 const size = { capsuleCollider.Size.x, ySize, 1.0f }; - glm::vec3 const position = { capsuleCollider.Offset.x, capsuleCollider.Offset.y, 1.0f }; - - glm::mat4 transformMatrix = transform.WorldSpaceTransformMatrix * glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), size); - - BatchRenderer2D::AddCircle(transformMatrix, color, thickness, -1); - } - } -} - -} diff --git a/TerranEngine/src/Scene/Systems/SceneRenderer.h b/TerranEngine/src/Scene/Systems/SceneRenderer.h deleted file mode 100644 index c5b0618b..00000000 --- a/TerranEngine/src/Scene/Systems/SceneRenderer.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "LibCore/Base.h" - -#include "Graphics/Camera.h" -#include "Graphics/Framebuffer.h" -#include "Graphics/Texture.h" - -#include "Scene/Components.h" -#include "Scene/Scene.h" - -namespace TerranEngine { - -class SceneRenderer final { -public: - SceneRenderer(FramebufferParameters const& params); - - void SetClearColor(glm::vec4 color) { m_ClearColor = color; } - - void SetScene(Scene* scene); - - void BeginScene(Camera& camera, glm::mat4 const& cameraTransform, bool inverseTransform); - - void SubmitSprite(SpriteRendererComponent const& spriteRenderer, glm::mat4& transform, int entityID); - void SubmitCircle(CircleRendererComponent const& circleRenderer, glm::mat4& transform, int entityID); - void SubmitLine(LineRendererComponent const& lineRenderer, int entityID); - - void SubmitText(TextRendererComponent const& textRenderer, glm::mat4& transform, int entityID); - - void EndScene(); - - Terran::Core::Shared& GetFramebuffer() { return m_Framebuffer; } - - void OnResize(uint32_t width, uint32_t height); - - void SetShowColliders(bool show) { m_ShowColliders = show; } - bool AreCollidersShowing() const { return m_ShowColliders; } - -private: - void SubmitColliderBounds(); - - Scene* m_Scene; - bool m_BegunScene = false; - bool m_ShowColliders = false; - - glm::vec4 m_ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f }; - - Terran::Core::Shared m_Framebuffer; -}; - -} - diff --git a/TerranEngine/src/Utils/SerializerUtils.cpp b/TerranEngine/src/Utils/SerializerUtils.cpp deleted file mode 100644 index 955be29b..00000000 --- a/TerranEngine/src/Utils/SerializerUtils.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "trpch.h" -#include "SerializerUtils.h" - -namespace TerranEngine { - -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec2 const& v) -{ - out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << YAML::EndSeq; - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec3 const& v) -{ - out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq; - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, glm::vec4 const& v) -{ - out << YAML::Flow; - out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq; - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, Terran::Core::UUID const& v) -{ - out << std::to_string(v); - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, TextureFilter const& v) -{ - out << TextureFilterToString(v); - return out; -} - -YAML::Emitter& operator<<(YAML::Emitter& out, std::byte v) -{ - out << static_cast(v); - return out; -} - -} diff --git a/Tests/LibAsset/AssetImporterRegistryTests.cpp b/Tests/LibAsset/AssetImporterRegistryTests.cpp new file mode 100644 index 00000000..982f51f0 --- /dev/null +++ b/Tests/LibAsset/AssetImporterRegistryTests.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +namespace Terran::Asset::Tests { + +class MockAsset final : public Asset { +public: + MockAsset(AssetId const& handle) + : Asset(handle) + { + } + TR_DECLARE_ASSET_TYPE(MockAsset); +}; + +class MockAsset2 final : public Asset { +public: + MockAsset2(AssetId const& handle) + : Asset(handle) + { + } + TR_DECLARE_ASSET_TYPE(MockAsset2); +}; + +class MockAssetImporter final : public AssetImporter { +public: + virtual ~MockAssetImporter() override = default; + [[nodiscard]] virtual AssetLoadResult load(AssetMetadata const& assetMetadata) override + { + Core::RefPtr asset = Core::RefPtr::create(assetMetadata.AssetId); + return { asset }; + } + virtual bool save(AssetMetadata const&, Core::RefPtr const&) override + { + return true; + } + [[nodiscard]] virtual bool can_handle(std::filesystem::path const&) override + { + return true; + } + [[nodiscard]] virtual AssetTypeId asset_type() override + { + return MockAsset::static_type(); + } +}; +class AssetImporterRegistryTest : public testing::Test { +protected: + AssetImporterRegistryTest() + { + m_asset_metadata.AssetId = Core::UUID(); + m_asset_metadata.Path = ""; + m_asset_metadata.Type = MockAsset::static_type(); + + m_asset_importer = Core::CreateShared(); + AssetImporterRegistry::register_asset(m_asset_importer); + } + virtual ~AssetImporterRegistryTest() override + { + AssetImporterRegistry::clear(); + } + + AssetMetadata m_asset_metadata; + Core::Shared m_asset_importer; +}; + +TEST_F(AssetImporterRegistryTest, load_calls_load_for_a_given_asset_importer_when_it_is_registered) +{ + auto assetRes = AssetImporterRegistry::load(m_asset_metadata); + ASSERT_TRUE(assetRes.is_ok()); + ASSERT_TRUE(assetRes.value()->is_valid()); + ASSERT_EQ(assetRes.value()->id(), m_asset_metadata.AssetId); +} + +TEST_F(AssetImporterRegistryTest, load_returns_error_when_asset_importer_of_a_given_type_is_not_registered) +{ + AssetMetadata metadata; + metadata.Type = MockAsset2::static_type(); + auto assetRes = AssetImporterRegistry::load(metadata); + ASSERT_FALSE(assetRes.is_ok()); + Core::Shared importerError = Core::CreateShared(AssetImporterError::Code::ImporterNotFound); + ASSERT_EQ(assetRes.error()->message(), importerError->message()); +} + +TEST_F(AssetImporterRegistryTest, save_calls_save_for_a_given_asset_importer_when_it_is_registered) +{ + Core::RefPtr mockAsset = Core::RefPtr::create(m_asset_metadata.AssetId); + bool result = AssetImporterRegistry::save(m_asset_metadata, mockAsset); + ASSERT_TRUE(result); +} + + +TEST_F(AssetImporterRegistryTest, save_returns_false_when_asset_importer_of_a_given_type_is_not_registered) +{ + Core::RefPtr mockAsset = Core::RefPtr::create(Core::UUID()); + AssetMetadata metadata; + metadata.Type = mockAsset->type(); + bool result = AssetImporterRegistry::save(metadata, mockAsset); + ASSERT_FALSE(result); +} + +TEST_F(AssetImporterRegistryTest, save_returns_false_when_assetmetadata_type_and_asset_type_dont_match) +{ + Core::RefPtr mockAsset = Core::RefPtr::create(Core::UUID()); + AssetMetadata metadata; + metadata.Type = MockAsset::static_type(); + bool result = AssetImporterRegistry::save(metadata, mockAsset); + ASSERT_FALSE(result); +} + +} diff --git a/Tests/LibAsset/Main.cpp b/Tests/LibAsset/Main.cpp new file mode 100644 index 00000000..64becff4 --- /dev/null +++ b/Tests/LibAsset/Main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Tests/LibAsset/premake5.lua b/Tests/LibAsset/premake5.lua new file mode 100644 index 00000000..dcddd247 --- /dev/null +++ b/Tests/LibAsset/premake5.lua @@ -0,0 +1,48 @@ +project "Test.LibAsset" +language "C++" +cppdialect "C++20" +kind "ConsoleApp" +staticruntime "Off" + +files { + "**.cpp", + "**.hpp", +} + +externalincludedirs { + "%{wks.location}/Libraries", + "%{Dependencies.spdlog.include}", + "%{Dependencies.glm.include}", + "%{Dependencies.yaml.include}", + "../../vendor/gtest/googletest/include/", +} + +links { + "LibCore", + "LibAsset", + "yaml-cpp", + "googletest", + "CoreFoundation.framework", -- no path needed for system frameworks + "Cocoa.framework", + "IOKit.framework", + "QuartzCore.framework", +} + +filter { "configurations:Debug" } +runtime "Debug" +symbols "On" +defines { "TERRAN_TESTING_DEBUG" } +defines "TR_DEBUG" +filter {} + +filter { "configurations:Release" } +runtime "Release" +defines { "TERRAN_TESTING_RELEASE" } + +filter { "system:macosx" } +architecture "ARM64" + +libdirs { "/usr/local/lib" } +links { + "CoreFoundation.framework", -- no path needed for system frameworks +} diff --git a/Tests/LibCore/ByteBufferTests.cpp b/Tests/LibCore/ByteBufferTests.cpp index e14b9219..2af64cc5 100644 --- a/Tests/LibCore/ByteBufferTests.cpp +++ b/Tests/LibCore/ByteBufferTests.cpp @@ -1,5 +1,3 @@ -#pragma once - #include #include diff --git a/Tests/LibCore/RefPtrTests.cpp b/Tests/LibCore/RefPtrTests.cpp new file mode 100644 index 00000000..64a612dc --- /dev/null +++ b/Tests/LibCore/RefPtrTests.cpp @@ -0,0 +1,169 @@ +#include + +#include + +namespace Terran::Tests { + +class MockRefCounted : public Core::RefCounted { +}; + +class RefPtrTest : public testing::Test { + +protected: + RefPtrTest() = default; + virtual ~RefPtrTest() override = default; +}; + +TEST_F(RefPtrTest, default_constructor_initializes_null_data) +{ + Core::RefPtr ref_ptr; + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, nullptr_t_constructor_initializes_null_data) +{ + Core::RefPtr ref_ptr(nullptr); + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, value_pointer_constructor_sets_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + ASSERT_EQ(ref_ptr.data(), mock_object); +} + +TEST_F(RefPtrTest, value_pointer_constructor_increments_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, reset_sets_null_if_ref_count_is_zero) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + + ref_ptr.reset(); + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, destructor_decrements_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + + { + Core::RefPtr temp_ref_ptr = ref_ptr; + ASSERT_EQ(mock_object->ref_count(), 2); + } + + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, release_exchanges_data_for_nullptr) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + MockRefCounted* data = ref_ptr.release(); + EXPECT_EQ(mock_object, data); + EXPECT_EQ(ref_ptr.data(), nullptr); + delete mock_object; +} + +TEST_F(RefPtrTest, copy_constructor_sets_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_EQ(temp_ref_ptr.data(), mock_object); +} + +TEST_F(RefPtrTest, copy_constructor_increments_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_EQ(mock_object->ref_count(), 2); +} + +TEST_F(RefPtrTest, move_constructor_sets_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(std::move(ref_ptr)); + ASSERT_EQ(temp_ref_ptr.data(), mock_object); +} + +TEST_F(RefPtrTest, move_constructor_releases_moved_data) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(std::move(ref_ptr)); + ASSERT_EQ(ref_ptr, nullptr); +} + +TEST_F(RefPtrTest, move_constructor_doesnt_increment_ref_count) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(std::move(ref_ptr)); + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, comparison_operator_returns_true_when_both_refptrs_point_to_same_object) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_TRUE(ref_ptr == temp_ref_ptr); +} + +TEST_F(RefPtrTest, comparison_operator_returns_false_when_both_refptrs_dont_point_to_same_object) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr; + ASSERT_FALSE(ref_ptr == temp_ref_ptr); +} + +TEST_F(RefPtrTest, nullptr_assignment_operator_sets_data_to_nullptr) +{ + Core::RefPtr ref_ptr = nullptr; + ASSERT_EQ(ref_ptr.data(), nullptr); +} + +TEST_F(RefPtrTest, nullptr_assignment_operator_decrements_refcount_when_previous_data_is_not_null) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr(ref_ptr); + ASSERT_EQ(mock_object->ref_count(), 2); + ref_ptr = nullptr; + ASSERT_EQ(mock_object->ref_count(), 1); +} +TEST_F(RefPtrTest, copy_assignment_operator_increments_refcount) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr = ref_ptr; + ASSERT_EQ(mock_object->ref_count(), 2); +} + +TEST_F(RefPtrTest, move_assignment_operator_doesnt_increase_refcount) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr(mock_object); + Core::RefPtr temp_ref_ptr = std::move(ref_ptr); + ASSERT_EQ(mock_object->ref_count(), 1); +} + +TEST_F(RefPtrTest, value_pointer_assignment_operator_doesnt_increase_refcount) +{ + MockRefCounted* mock_object = new MockRefCounted(); + Core::RefPtr ref_ptr = mock_object; + ASSERT_EQ(mock_object->ref_count(), 1); +} + +} diff --git a/Tests/LibScene/EntityTests.cpp b/Tests/LibScene/EntityTests.cpp new file mode 100644 index 00000000..928f7112 --- /dev/null +++ b/Tests/LibScene/EntityTests.cpp @@ -0,0 +1,194 @@ +#include +#include + +#include +#include +#include +#include + +#include +namespace Terran::World::Tests { + +class EntityTest : public testing::Test { + +protected: + EntityTest() + : m_scene(Core::RefPtr::create(m_event_dispatcher)) + { + } + + virtual ~EntityTest() override = default; + + Core::EventDispatcher m_event_dispatcher; + Core::RefPtr m_scene; +}; + +TEST_F(EntityTest, id_getter_returns_invalid_if_entity_doesnt_have_tag_component) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_FALSE((bool)entity.id()); +} + +TEST_F(EntityTest, id_getter_returns_valid_if_entity_has_tag_component) +{ + Entity entity = m_scene->create_entity(); + ASSERT_TRUE((bool)entity.id()); +} + +TEST_F(EntityTest, set_parent_makes_child_report_correct_parent) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + ASSERT_EQ(child.parent(), parent); +} + +TEST_F(EntityTest, has_parent_returns_false_when_entity_doesnt_have_relationship_component) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_FALSE(entity.has_parent()); +} + +TEST_F(EntityTest, entity_reports_correct_scene_id) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_EQ(entity.scene_id(), m_scene->id()); +} + +TEST_F(EntityTest, set_parent_adds_child_to_parents_children_list) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + ASSERT_EQ(parent.children().size(), 1u); +} + +TEST_F(EntityTest, child_at_returns_invalid_when_entity_doesnt_have_relationship_component) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_FALSE((bool)entity.child_at(0)); +} + +TEST_F(EntityTest, parent_getter_returns_invalid_when_entity_doesnt_have_relationship_component) +{ + Entity entity = m_scene->create_entity("Parent"); + ASSERT_FALSE((bool)entity.parent()); +} + +TEST_F(EntityTest, child_is_child_of_parent) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + ASSERT_TRUE(child.is_child_of(parent)); +} + +TEST_F(EntityTest, unparent_removes_child_from_parent) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + child.unparent(); + ASSERT_EQ(parent.children().size(), 0u); +} + +TEST_F(EntityTest, unparent_clears_childs_parent_reference) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + child.unparent(); + ASSERT_FALSE(child.has_parent()); +} + +TEST_F(EntityTest, unparent_returns_error_if_entity_doesnt_have_relationship_component) +{ + Entity child = m_scene->create_entity("Child"); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::DoesntHaveRelationshipComponent); +} + +TEST_F(EntityTest, unparent_returns_error_if_entity_parent_isnt_found) +{ + Entity child = m_scene->create_entity("Child"); + auto& relationshipComponent = child.add_component(); + relationshipComponent.Parent = Core::UUID(); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::ParentNotFound); +} + +// NOTE: This will happen in a very wierd case, +// you'd have to mess with the Relationship Component directly, +// which is not really recommended, +// still though lets check if the logic to deal with it is correct + +TEST_F(EntityTest, unparent_returns_error_if_parent_doesnt_contain_child) +{ + Entity parent = m_scene->create_entity("Parent"); + parent.add_component(); + Entity child = m_scene->create_entity("Child"); + auto& relationshipComponent = child.add_component(); + relationshipComponent.Parent = parent.id(); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::ParentDoesntContainChild); +} + +TEST_F(EntityTest, unparent_returns_error_if_parent_doesnt_have_relationship_component) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + auto& relationshipComponent = child.add_component(); + relationshipComponent.Parent = parent.id(); + auto unparentRes = child.unparent(); + ASSERT_FALSE(unparentRes.is_ok()); + ASSERT_EQ(unparentRes.error(), EntityErrors::DoesntHaveRelationshipComponent); +} + +TEST_F(EntityTest, set_parent_does_not_create_cycle_when_parent_is_already_child) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + a.set_parent(b); + // Attempting to make b a child of a should be a no-op (cycle guard) + auto parentRes = b.set_parent(a); + ASSERT_FALSE(b.is_child_of(a)); + ASSERT_FALSE(parentRes.is_ok()); + ASSERT_EQ(parentRes.error(), EntityErrors::TargetParentIsAlreadyAChildOfThisEntity); +} + +TEST_F(EntityTest, set_parent_returns_error_if_entity_is_already_a_child_of_this_parent) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + a.set_parent(b); + auto parentRes = a.set_parent(b); + ASSERT_FALSE(parentRes.is_ok()); + ASSERT_EQ(parentRes.error(), EntityErrors::AlreadyAChildOfThisParent); +} + +TEST_F(EntityTest, set_parent_unparents_if_entity_alread_has_another_parent) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + Entity c = m_scene->create_entity("C"); + a.set_parent(b); + a.set_parent(c); + ASSERT_TRUE(b.children().empty()); + ASSERT_TRUE(a.is_child_of(c)); +} + +TEST_F(EntityTest, reparent_moves_child_to_new_parent) +{ + Entity parent1 = m_scene->create_entity("Parent1"); + Entity parent2 = m_scene->create_entity("Parent2"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent1); + child.reparent(parent2); + ASSERT_TRUE(child.is_child_of(parent2)); + ASSERT_EQ(parent1.children().size(), 0u); +} + +} diff --git a/Tests/LibScene/Main.cpp b/Tests/LibScene/Main.cpp new file mode 100644 index 00000000..64becff4 --- /dev/null +++ b/Tests/LibScene/Main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Tests/LibScene/SceneTests.cpp b/Tests/LibScene/SceneTests.cpp new file mode 100644 index 00000000..78ca5668 --- /dev/null +++ b/Tests/LibScene/SceneTests.cpp @@ -0,0 +1,268 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Terran::World::Tests { + +class SceneTest : public testing::Test { + +protected: + + void on_scene_start(SceneStartSimulationEvent&) { + m_is_scene_start_event_triggered = true; + } + + void on_scene_stop(SceneStopSimulationEvent&) { + m_is_scene_stop_event_triggered = true; + } + + SceneTest() + : m_scene(Core::RefPtr::create(m_event_dispatcher)) + { + m_event_dispatcher.handlers().connect<&SceneTest::on_scene_start>(this); + m_event_dispatcher.handlers().connect<&SceneTest::on_scene_stop>(this); + } + virtual ~SceneTest() override = default; + + Core::EventDispatcher m_event_dispatcher; + Core::RefPtr m_scene; + bool m_is_scene_start_event_triggered = false; + bool m_is_scene_stop_event_triggered = false; +}; + +TEST_F(SceneTest, create_empty_entity_returns_valid_entity) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_TRUE((bool)entity); +} + +TEST_F(SceneTest, create_empty_entity_doesnt_add_tag_component) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_FALSE(entity.has_component()); +} + +TEST_F(SceneTest, create_empty_entity_doesnt_add_transform_component) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_FALSE(entity.has_component()); +} + +TEST_F(SceneTest, create_empty_entity_doesnt_add_entity_to_scene_map) +{ + Entity entity = m_scene->create_empty_entity(); + ASSERT_TRUE(m_scene->entity_map().empty()); +} + +TEST_F(SceneTest, create_entity_without_name_returns_valid_entity) +{ + Entity entity = m_scene->create_entity(); + ASSERT_TRUE((bool)entity); +} + +TEST_F(SceneTest, create_entity_with_empty_name_defaults_to_entity) +{ + Entity entity = m_scene->create_entity(); + ASSERT_EQ(entity.name(), "Entity"); +} + +TEST_F(SceneTest, create_entity_with_name_returns_valid_entity) +{ + Entity entity = m_scene->create_entity("TestEntity"); + ASSERT_TRUE((bool)entity); +} + +TEST_F(SceneTest, create_entity_with_adds_tag_component) +{ + Entity entity = m_scene->create_entity("Tagged"); + ASSERT_TRUE(entity.has_component()); +} + +TEST_F(SceneTest, create_entity_adds_transform_component) +{ + Entity entity = m_scene->create_entity("WithTransform"); + ASSERT_TRUE(entity.has_component()); +} + +TEST_F(SceneTest, create_entity_with_name_stores_correct_name) +{ + Entity entity = m_scene->create_entity("TestEntity"); + ASSERT_EQ(entity.name(), "TestEntity"); +} + +TEST_F(SceneTest, create_entity_with_explicit_uuid_sets_uuid) +{ + Core::UUID uuid; + Entity entity = m_scene->create_entity("Identified", uuid); + ASSERT_EQ(entity.id(), uuid); +} + +TEST_F(SceneTest, create_entity_adds_entity_to_scene_map) +{ + Core::UUID uuid; + m_scene->create_entity("Identified", uuid); + Entity found = m_scene->find_entity(uuid); + ASSERT_TRUE((bool)found); +} + +TEST_F(SceneTest, destroy_entity_removes_it_from_entity_map) +{ + Entity entity = m_scene->create_entity("ToDestroy"); + Core::UUID uuid = entity.id(); + m_scene->destrory_entity(entity); + Entity found = m_scene->find_entity(uuid); + ASSERT_FALSE((bool)found); +} + +TEST_F(SceneTest, destroy_entity_makes_it_invalid) +{ + Entity entity = m_scene->create_entity("ToDestroy"); + m_scene->destrory_entity(entity); + ASSERT_FALSE(entity.valid()); +} + +TEST_F(SceneTest, destroy_entity_doesnt_erase_from_entity_map_if_entity_wasnt_registered_in_it) +{ + Entity entity = m_scene->create_entity("ToDestroy"); + Entity empty_entity = m_scene->create_empty_entity(); + ASSERT_FALSE(m_scene->entity_map().empty()); + m_scene->destrory_entity(empty_entity); + ASSERT_FALSE(m_scene->entity_map().empty()); +} + +TEST_F(SceneTest, two_entities_have_different_uuids) +{ + Entity a = m_scene->create_entity("A"); + Entity b = m_scene->create_entity("B"); + ASSERT_NE(a.id(), b.id()); +} + +TEST_F(SceneTest, find_entity_by_uuid_returns_correct_entity) +{ + Entity entity = m_scene->create_entity("FindMe"); + Entity found = m_scene->find_entity(entity.id()); + ASSERT_EQ(entity, found); +} + +TEST_F(SceneTest, find_entity_by_name_returns_correct_entity) +{ + Entity entity = m_scene->create_entity("FindByName"); + Entity found = m_scene->find_entity("FindByName"); + ASSERT_EQ(entity, found); +} + +TEST_F(SceneTest, find_entity_by_invalid_uuid_returns_null_entity) +{ + Core::UUID nonexistent; + Entity found = m_scene->find_entity(nonexistent); + ASSERT_FALSE((bool)found); +} + +TEST_F(SceneTest, find_entity_by_nonexistent_name_returns_null_entity) +{ + Entity found = m_scene->find_entity("DoesNotExist"); + ASSERT_FALSE((bool)found); +} + +TEST_F(SceneTest, destroy_parent_also_destroys_children) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + Core::UUID child_uuid = child.id(); + m_scene->destrory_entity(parent); + Entity found_child = m_scene->find_entity(child_uuid); + ASSERT_FALSE((bool)found_child); +} + +TEST_F(SceneTest, destroy_child_removes_itself_from_parent_children) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + m_scene->destrory_entity(child); + ASSERT_TRUE(parent.children().empty()); +} + +TEST_F(SceneTest, duplicate_entity_creates_new_entity) +{ + Entity original = m_scene->create_entity("Original"); + Entity copy = m_scene->duplicate_entity(original); + ASSERT_NE(original, copy); +} + +TEST_F(SceneTest, duplicate_entity_copy_has_different_uuid) +{ + Entity original = m_scene->create_entity("Original"); + Entity copy = m_scene->duplicate_entity(original); + ASSERT_NE(original.id(), copy.id()); +} + +TEST_F(SceneTest, duplicate_entity_copy_name_is_original_name_with_copy_suffix) +{ + Entity original = m_scene->create_entity("MyEntity"); + Entity copy = m_scene->duplicate_entity(original); + ASSERT_EQ(copy.name(), "MyEntity Copy"); +} + +TEST_F(SceneTest, duplicate_entity_with_children_duplicates_children) +{ + Entity parent = m_scene->create_entity("Parent"); + Entity child = m_scene->create_entity("Child"); + child.set_parent(parent); + Entity copy = m_scene->duplicate_entity(parent); + ASSERT_EQ(copy.children().size(), 1u); +} + +TEST_F(SceneTest, scene_is_not_playing_by_default) +{ + ASSERT_FALSE(m_scene->playing()); +} + +TEST_F(SceneTest, start_runtime_sets_playing_to_true) +{ + m_scene->start_runtime(); + ASSERT_TRUE(m_scene->playing()); +} + +TEST_F(SceneTest, start_runtime_triggers_scene_start_simulation_event) +{ + m_scene->start_runtime(); + ASSERT_TRUE(m_is_scene_start_event_triggered); +} + +TEST_F(SceneTest, stop_runtime_sets_playing_to_false) +{ + m_scene->start_runtime(); + m_scene->stop_runtime(); + ASSERT_FALSE(m_scene->playing()); +} + +TEST_F(SceneTest, stop_runtime_triggers_scene_stop_simulation_event) +{ + m_scene->start_runtime(); + m_scene->stop_runtime(); + ASSERT_TRUE(m_is_scene_stop_event_triggered); +} + +TEST_F(SceneTest, calling_start_runtime_twice_keeps_playing_true) +{ + m_scene->start_runtime(); + m_scene->start_runtime(); + ASSERT_TRUE(m_scene->playing()); +} + +TEST_F(SceneTest, calling_stop_runtime_without_starting_keeps_playing_false) +{ + m_scene->stop_runtime(); + ASSERT_FALSE(m_scene->playing()); +} + +} diff --git a/Tests/LibScene/premake5.lua b/Tests/LibScene/premake5.lua new file mode 100644 index 00000000..4f07cc8b --- /dev/null +++ b/Tests/LibScene/premake5.lua @@ -0,0 +1,50 @@ +project "Test.LibScene" +language "C++" +cppdialect "C++20" +kind "ConsoleApp" +staticruntime "Off" + +files { + "**.cpp", + "**.hpp", +} + +externalincludedirs { + "%{wks.location}/Libraries", + "%{Dependencies.spdlog.include}", + "%{Dependencies.glm.include}", + "%{Dependencies.yaml.include}", + "%{Dependencies.entt.include}", + "../../vendor/gtest/googletest/include/", +} + +links { + "LibCore", + "LibAsset", + "LibScene", + "yaml-cpp", + "googletest", + "CoreFoundation.framework", -- no path needed for system frameworks + "Cocoa.framework", + "IOKit.framework", + "QuartzCore.framework", +} + +filter { "configurations:Debug" } +runtime "Debug" +symbols "On" +defines { "TERRAN_TESTING_DEBUG" } +defines "TR_DEBUG" +filter {} + +filter { "configurations:Release" } +runtime "Release" +defines { "TERRAN_TESTING_RELEASE" } + +filter { "system:macosx" } +architecture "ARM64" + +libdirs { "/usr/local/lib" } +links { + "CoreFoundation.framework", -- no path needed for system frameworks +} diff --git a/dependecies.lua b/dependecies.lua index e8f094c4..7f2a689a 100644 --- a/dependecies.lua +++ b/dependecies.lua @@ -15,13 +15,15 @@ Dependencies = { include = "%{wks.location}/vendor/GLFW/include", link = "glfw", }, + entt = { + include = "%{wks.location}/vendor/entt/include", + }, } IncludeDirectories = {} IncludeDirectories["imgui"] = "%{wks.location}/TerranEngine/vendor/ImGui/" IncludeDirectories["glad"] = "%{wks.location}/TerranEngine/vendor/GLAD/include/" IncludeDirectories["stb"] = "%{wks.location}/TerranEngine/vendor/stb/" -IncludeDirectories["entt"] = "%{wks.location}/TerranEngine/vendor/entt/include/" IncludeDirectories["msdfgen"] = "%{wks.location}/TerranEngine/vendor/msdf-atlas-gen/msdfgen/" IncludeDirectories["msdf_atlas_gen"] = "%{wks.location}/TerranEngine/vendor/msdf-atlas-gen/" IncludeDirectories["box2d"] = "%{wks.location}/TerranEngine/vendor/Box2D/include/" diff --git a/premake-native.lua b/premake-native.lua index 5ca6f51c..02a78293 100644 --- a/premake-native.lua +++ b/premake-native.lua @@ -35,6 +35,7 @@ group "Core" include "Libraries/LibMain" include "Libraries/LibCore" include "Libraries/LibAsset" +include "Libraries/LibScene" include "TerranEngine" group "Graphics" @@ -50,3 +51,5 @@ include "Sandbox" group "Tests" include "Tests/LibCore" +include "Tests/LibScene" +include "Tests/LibAsset" diff --git a/TerranEngine/vendor/entt/LICENSE b/vendor/entt/LICENSE similarity index 100% rename from TerranEngine/vendor/entt/LICENSE rename to vendor/entt/LICENSE diff --git a/TerranEngine/vendor/entt/include/entt.hpp b/vendor/entt/include/entt.hpp similarity index 100% rename from TerranEngine/vendor/entt/include/entt.hpp rename to vendor/entt/include/entt.hpp