diff --git a/Coral.vcxproj b/Coral.vcxproj index f9b674a0..2a1baef8 100644 --- a/Coral.vcxproj +++ b/Coral.vcxproj @@ -302,6 +302,10 @@ + + true + true + @@ -1025,7 +1029,6 @@ - diff --git a/Include/EditorSystems/AssetEditorSystems/AssetEditorSystem.h b/Include/EditorSystems/AssetEditorSystems/AssetEditorSystem.h index 6f565c0d..db958120 100644 --- a/Include/EditorSystems/AssetEditorSystems/AssetEditorSystem.h +++ b/Include/EditorSystems/AssetEditorSystems/AssetEditorSystem.h @@ -1,563 +1,208 @@ -#include "Utilities/Time.h" -#ifdef EDITOR #pragma once +#ifdef EDITOR #include "EditorSystems/EditorSystem.h" +#include + +#include "Utilities/Time.h" #include "Assets/Asset.h" #include "Assets/Core/AssetSaveInfo.h" -#include "Assets/Core/AssetLoadInfo.h" -#include "Core/AssetManager.h" #include "Core/Editor.h" -#include "Core/Input.h" #include "Meta/MetaType.h" -#include "Utilities/view_istream.h" -#include "Utilities/DoUndo.h" -#include "Utilities/StringFunctions.h" namespace CE { - /* - Do not derive from this, derive from the templated class below. - */ - class AssetEditorSystemInterface + class AssetEditorMementoStack { public: + AssetEditorMementoStack() = default; + AssetEditorMementoStack(const AssetEditorMementoStack&) = delete; + AssetEditorMementoStack(AssetEditorMementoStack&&) noexcept = default; - /* - For documentation on any of these functions, see the overriden functions in - AssetEditorSystem - */ + AssetEditorMementoStack& operator=(const AssetEditorMementoStack&) = delete; + AssetEditorMementoStack& operator=(AssetEditorMementoStack&&) noexcept = default; - virtual AssetSaveInfo SaveToMemory() = 0; - virtual void SaveToFile() = 0; - virtual TypeId GetAssetTypeId() const = 0; + ~AssetEditorMementoStack() = default; - protected: - friend class Editor; - struct MementoAction + struct SimilarityToFile { - void Do(); - void Undo(); - void RefreshTheAssetEditor(); + bool mDoesStateMatchFile{}; + std::filesystem::file_time_type mFileWriteTimeLastTimeWeChecked{}; + }; + struct Action + { std::string mState{}; - bool mDoIsNeeded{}; - - // If the engine was refreshed, - // we should re-serialize this - // state in order to take into - // account any changes made to - // any assets. bool mRequiresReserialization{}; + SimilarityToFile mSimilarityToFile{}; + }; - std::string mNameOfAssetEditor{}; + void Do(std::shared_ptr action); - // Mutable because the result is cached, but it does not influence the state - // of the asset. - mutable bool mIsSameAsFile{}; - mutable std::filesystem::file_time_type mTimeWeCheckedIfIsSameAsFile{}; - }; + bool TryUndo(); + bool TryRedo(); - using MementoStack = DoUndo::DoUndoStackBase; + void ClearRedo(); - void SetMementoStack(MementoStack&& stack) { mMementoStack = std::move(stack); }; - virtual MementoStack&& ExtractMementoStack() = 0; + std::shared_ptr GetMostRecentState() const; - MementoStack mMementoStack{}; + std::vector> mActions{}; + size_t mNumOfActionsDone{}; }; - - /* - An EditorSystem specialized for editing assets. Deriving from this - will link the typename to your derived AssetEditor class, which means - that clicking on the Asset in the contentbrowser will create an instance - of your system. - */ - template - class AssetEditorSystem : - public EditorSystem, - public AssetEditorSystemInterface + class AssetEditorSystemBase : + public EditorSystem { public: - AssetEditorSystem(T&& asset); - ~AssetEditorSystem() override = default; + AssetEditorSystemBase(const Asset& asset); - const T& GetAsset() const { return mAsset; } - T& GetAsset() { return mAsset; } + ~AssetEditorSystemBase(); /* AssetEditorSystems generally work on a copy of the original asset, in order to prevent your changes leading to unexpected behaviour elsewhere. This function return the orginal asset, if it exists. */ - WeakAssetHandle TryGetOriginalAsset() const; + WeakAssetHandle TryGetOriginalAsset() const; + + std::optional GetDestinationFile() const; /* Saves the asset to file at the end of the frame. Will do a 'restart' of the engine that is completely hidden from the users, but this restart makes sure your changes are correctly applied throughout the engine. - - If no file location is provided, the file location of the - original asset will be used. */ - void SaveToFile() final; + void SaveToFile(); /* Saves the asset to memory. The resulting AssetSaveInfo can be used to construct a copy of the asset. - - Note that calling AssetSaveInfo::SaveToFile will not trigger - the engine to restart, which means your changes may not be - applied correctly, if at all. Prefer AssetEditorSystem::SaveToFile - if you intend to save this asset to a file. */ - [[nodiscard]] AssetSaveInfo SaveToMemory() final; + AssetSaveInfo SaveToMemory(); bool IsSavedToFile() const; - //********************************// - // Virtual functions // - //********************************// + AssetEditorMementoStack ExtractStack(); - void Tick(float deltaTime) override; + void InsertMementoStack(AssetEditorMementoStack stack); protected: bool Begin(ImGuiWindowFlags flags) override; + void End() override; + + void ShowSaveButton(); + private: + friend ReflectAccess; + static MetaType Reflect(); + REFLECT_AT_START_UP(AssetEditorSystemBase); + + virtual const Asset& GetAsset() const = 0; + virtual Asset& GetAsset() = 0; + /* Often the changes aren't directly made to the asset; for example you will be editing a world, moving some entities, deleting some others, but you are not - directly modifing the asset, instead storing the changes inside of the system. + directly modifing the asset, instead storing the changes inside the system. This function alerts you that any changes you may have made must be applied to the asset now. */ - virtual void ApplyChangesToAsset() {}; + virtual void ApplyChangesToAsset() {} - protected: - void ShowSaveButton(); - - T mAsset; + virtual std::unique_ptr> ConstructAsset(AssetLoadInfo& loadInfo) = 0; - std::filesystem::path mPathToSaveAssetTo{}; - - private: - TypeId GetAssetTypeId() const final { return MakeTypeId(); }; - - friend ReflectAccess; - static MetaType Reflect() - { - // Bind the asset to our editor - Editor::RegisterAssetEditorSystem(); - return MetaType{ MetaType::T>{}, Format("AssetEditorSystem<{}>", MakeTypeName()), MetaType::Base{} }; - } - - /* - * Checks if any changes have been made to our asset, and saves them to the do/undo stack. - * This allows each action to be undone by reverting to an older version. - * - * Is by default called on a cooldown, there is generally no need to call this yourself. - */ void CheckForDifferences(); - void CompleteDifferenceCheckCycle(); - - MementoStack&& ExtractMementoStack() override; - /* * Some assets are different every time we load them. entt::registry for example may completely * shuffle all the entities around as it wishes. So we load and save the asset to account for this. */ - static std::string Reserialize(std::string_view serialized); + std::string Reserialize(std::string_view serialized); - - /** - * \brief Checking for differences involves saving/loading the asset a few times. We spread this out - * over several frames to reduce the impact of the frame drops. - */ - struct DifferenceCheckState - { - enum class Stage - { - SaveToMemory, - ReloadFromMemory, - ResaveToMemory, - Compare, - CheckIfSavedToFile, - NUM_OF_STAGES, - FirstStage = SaveToMemory - }; - MementoAction mAction{}; - Stage mStage{}; - std::optional mTemporarilyDeserializedAsset{}; - }; - DifferenceCheckState mDifferenceCheckState{}; - Cooldown mUpdateStateCooldown{ .2f }; - - // Used for checking if our asset has unsaved changes. Kept in memory for performance reasons. - // May not always be up to date, it's updated when needed. - struct AssetOnFile - { - std::string mReserializedAsset{}; - - // If changes are made to the asset file, this mAssetAsSeenOnFile is updated. - std::filesystem::file_time_type mWriteTimeAtTimeOfReserializing{}; - }; - mutable AssetOnFile mAssetOnFile{}; - }; + AssetEditorMementoStack mMementoStack{}; - namespace Internal - { - std::string GetSystemNameBasedOnAssetName(std::string_view assetName); - std::string GetAssetNameBasedOnSystemName(std::string_view systemName); - } + static constexpr float sDifferenceCheckCooldown = 1.0f; + Timer mDifferenceCheckTimer{}; - inline void AssetEditorSystemInterface::MementoAction::Do() - { - if (!mDoIsNeeded) + struct CachedFile { - return; - } - - RefreshTheAssetEditor(); - } - - inline void AssetEditorSystemInterface::MementoAction::Undo() - { - mDoIsNeeded = true; - RefreshTheAssetEditor(); - } - - inline void AssetEditorSystemInterface::MementoAction::RefreshTheAssetEditor() - { - // The refreshing will return the asset editor to the - // top state in the stack - Editor::Get().Refresh({ 0, {}, mNameOfAssetEditor }); - } - - template - AssetEditorSystem::AssetEditorSystem(T&& asset) : - EditorSystem(Internal::GetSystemNameBasedOnAssetName(asset.GetName())), - mAsset(std::move(asset)), - mPathToSaveAssetTo([this]() -> std::filesystem::path - { - WeakAssetHandle originalAsset = TryGetOriginalAsset(); - - if (originalAsset != nullptr) - { - return originalAsset.GetFileOfOrigin().value_or(std::filesystem::path{}); - } - return {}; - } - ()) - { - } + std::filesystem::file_time_type mLastWriteTime{}; + std::string mFileContents{}; + }; + CachedFile mCachedFile{}; - template - WeakAssetHandle AssetEditorSystem::TryGetOriginalAsset() const - { - return AssetManager::Get().TryGetWeakAsset(mAsset.GetName()); - } + std::future> mActionToAdd{}; - template - void AssetEditorSystem::SaveToFile() - { - if (mPathToSaveAssetTo.empty()) - { - LOG(LogEditor, Warning, "Could not save asset {} - The file location was empty. This is caused by there not being an original asset, and mPathToSaveAssetTo was never updated.", - mAsset.GetName()); - return; - } - - Editor::Get().Refresh( - { - Editor::RefreshRequest::Volatile, - - // A shared pointer, because it makes move semantics with lambdas so much simpler - [assetSaveInfo = std::make_shared(SaveToMemory()), saveTo = mPathToSaveAssetTo] - { - assetSaveInfo->SaveToFile(saveTo); - } - }); - } + std::mutex mAssetMutex{}; + }; + /* + An EditorSystem specialized for editing assets. Deriving from this + will link the typename to your derived AssetEditor class, which means + that clicking on the Asset in the contentbrowser will create an instance + of your system. + */ template - AssetSaveInfo AssetEditorSystem::SaveToMemory() + class AssetEditorSystem : + public AssetEditorSystemBase { - ApplyChangesToAsset(); - - std::optional importerInfo{}; - - if (const WeakAssetHandle originalAsset = TryGetOriginalAsset(); originalAsset != nullptr) - { - importerInfo = originalAsset.GetMetaData().GetImporterInfo(); - - if (importerInfo.has_value()) - { - importerInfo->mWereEditsMadeAfterImporting |= !IsSavedToFile(); - } - } + public: + AssetEditorSystem(T&& asset); + ~AssetEditorSystem() override = default; - AssetSaveInfo saveInfo = mAsset.Save(std::move(importerInfo)); - return saveInfo; - } + //********************************// + // Virtual functions // + //********************************// - template - bool AssetEditorSystem::IsSavedToFile() const - { - const MementoAction* const topAction = mMementoStack.PeekTop(); - return topAction == nullptr ? true : topAction->mIsSameAsFile; - } + void Tick(float deltaTime) override; - template - void AssetEditorSystem::Tick(float deltaTime) - { - EditorSystem::Tick(deltaTime); + protected: + T mAsset; - const bool checkForDifferences = mUpdateStateCooldown.IsReady(deltaTime); + private: + const T& GetAsset() const final { return mAsset; } + T& GetAsset() final { return mAsset; } - if (!Input::Get().HasFocus()) - { - return; - } + friend ReflectAccess; + static MetaType Reflect(); - if (Input::Get().IsKeyboardKeyHeld(Input::KeyboardKey::LeftControl) - || Input::Get().IsKeyboardKeyHeld(Input::KeyboardKey::RightControl)) - { - if (mMementoStack.GetNumOfActionsDone() > 1 - && mMementoStack.CanUndo() - && Input::Get().WasKeyboardKeyPressed(Input::KeyboardKey::Z)) - { - mMementoStack.Undo(); - mUpdateStateCooldown.mAmountOfTimePassed = 0.0f; - mDifferenceCheckState.mStage = DifferenceCheckState::Stage::FirstStage; - return; - } - - if (mMementoStack.CanRedo() - && Input::Get().WasKeyboardKeyPressed(Input::KeyboardKey::Y)) - { - mMementoStack.Redo(); - mUpdateStateCooldown.mAmountOfTimePassed = 0.0f; - mDifferenceCheckState.mStage = DifferenceCheckState::Stage::FirstStage; - return; - } - - if (Input::Get().WasKeyboardKeyPressed(Input::KeyboardKey::S)) - { - SaveToFile(); - } - } - - if (checkForDifferences) - { - CheckForDifferences(); - } - } + std::unique_ptr> ConstructAsset(AssetLoadInfo& loadInfo) final; + }; - template - bool AssetEditorSystem::Begin(ImGuiWindowFlags flags) + namespace Internal { - return EditorSystem::Begin(flags | (IsSavedToFile() ? 0 : ImGuiWindowFlags_UnsavedDocument)); + std::string GetSystemNameBasedOnAssetName(std::string_view assetName); + std::string GetAssetNameBasedOnSystemName(std::string_view systemName); } template - void AssetEditorSystem::ShowSaveButton() - { - if (ImGui::Button(ICON_FA_FLOPPY_O)) - { - // If you're looking at this because - // you want to run logic before saving, look at ApplyChangesToAsset - SaveToFile(); - } - } - - template - void AssetEditorSystem::CheckForDifferences() + AssetEditorSystem::AssetEditorSystem(T&& asset) : + AssetEditorSystemBase(asset), + mAsset(std::move(asset)) { - mUpdateStateCooldown.mAmountOfTimePassed = 0.0f; - - switch (mDifferenceCheckState.mStage) - { - case DifferenceCheckState::Stage::SaveToMemory: - { - mDifferenceCheckState.mAction.mNameOfAssetEditor = GetName(); - mDifferenceCheckState.mAction.mState = SaveToMemory().ToString(); - - if (mMementoStack.PeekTop() != nullptr - && mDifferenceCheckState.mAction.mState == mMementoStack.PeekTop()->mState) - { - mUpdateStateCooldown.mAmountOfTimePassed = -mUpdateStateCooldown.mCooldown * (static_cast(DifferenceCheckState::Stage::NUM_OF_STAGES) - 1.0f); - mDifferenceCheckState.mStage = DifferenceCheckState::Stage::CheckIfSavedToFile; - return; - } - - break; - } - case DifferenceCheckState::Stage::ReloadFromMemory: - { - std::optional loadInfo = AssetLoadInfo::LoadFromStream(std::make_unique(mDifferenceCheckState.mAction.mState)); - - if (!loadInfo.has_value()) - { - LOG(LogEditor, Error, "Failed to load metadata, metadata was invalid somehow?"); - mDifferenceCheckState.mStage = DifferenceCheckState::Stage::CheckIfSavedToFile; - return; - } - - mDifferenceCheckState.mTemporarilyDeserializedAsset.emplace(*loadInfo); - break; - } - case DifferenceCheckState::Stage::ResaveToMemory: - { - mDifferenceCheckState.mAction.mState = mDifferenceCheckState.mTemporarilyDeserializedAsset->Save().ToString(); - break; - } - case DifferenceCheckState::Stage::Compare: - { - MementoAction* const topAction = mMementoStack.PeekTop(); - - if (topAction == nullptr) - { - // If this is the first action, it is likely - // going to match the file exactly. - // We check in more detail in the next stage. - mDifferenceCheckState.mAction.mIsSameAsFile = true; - - mMementoStack.Do(std::move(mDifferenceCheckState.mAction)); - break; - } - - if (topAction->mRequiresReserialization) - { - topAction->mState = Reserialize(topAction->mState); - topAction->mRequiresReserialization = false; - } - - if (topAction->mState != mDifferenceCheckState.mAction.mState) - { - LOG(LogEditor, Verbose, "Change detected for {}", GetName()); - - // A change was made, it's unlikely going to match - // the file. We check in more detail in the next stage. - mDifferenceCheckState.mAction.mIsSameAsFile = false; - - mMementoStack.Do(std::move(mDifferenceCheckState.mAction)); - } - - break; - } - case DifferenceCheckState::Stage::CheckIfSavedToFile: - { - MementoAction* const topAction = mMementoStack.PeekTop(); - - if (topAction == nullptr) - { - break; - } - - if (!std::filesystem::exists(mPathToSaveAssetTo)) - { - topAction->mIsSameAsFile = false; - break; - } - - const std::filesystem::file_time_type lastWriteTime = std::filesystem::last_write_time(mPathToSaveAssetTo); - - if (topAction->mTimeWeCheckedIfIsSameAsFile == lastWriteTime) - { - break; - } - - topAction->mTimeWeCheckedIfIsSameAsFile = lastWriteTime; - - if (mAssetOnFile.mWriteTimeAtTimeOfReserializing != lastWriteTime) - { - // Otherwise we load and save the file. - LOG(LogEditor, Verbose, "Loading asset {} from file to check if it's unsaved...", mAsset.GetName()); - - std::optional loadInfo = AssetLoadInfo::LoadFromFile(mPathToSaveAssetTo); - - if (!loadInfo.has_value()) - { - LOG(LogEditor, Error, "Could not load asset from file {}", mPathToSaveAssetTo.string()); - break; - } - - T assetAsSeenOnFile{ *loadInfo }; - mAssetOnFile.mReserializedAsset = Reserialize(assetAsSeenOnFile.Save().ToString()); - mAssetOnFile.mWriteTimeAtTimeOfReserializing = lastWriteTime; - } - - topAction->mIsSameAsFile = mAssetOnFile.mReserializedAsset == topAction->mState; - - break; - } - case DifferenceCheckState::Stage::NUM_OF_STAGES:; - } - - mDifferenceCheckState.mStage = static_cast(static_cast(mDifferenceCheckState.mStage) + 1); - - if (mDifferenceCheckState.mStage == DifferenceCheckState::Stage::NUM_OF_STAGES) - { - mDifferenceCheckState.mStage = DifferenceCheckState::Stage::FirstStage; - } } template - void AssetEditorSystem::CompleteDifferenceCheckCycle() + void AssetEditorSystem::Tick(float deltaTime) { - for (int i = 0; i < static_cast(DifferenceCheckState::Stage::NUM_OF_STAGES) * 2; i++) - { - CheckForDifferences(); - } + AssetEditorSystemBase::Tick(deltaTime); } template - AssetEditorSystemInterface::MementoStack&& AssetEditorSystem::ExtractMementoStack() + MetaType AssetEditorSystem::Reflect() { - // It's possible an action was commited in the last .5f seconds, the change - // has not been registered by the do-undo stack and would be ignored. - if (mMementoStack.PeekTop() == nullptr - || (mUpdateStateCooldown.mAmountOfTimePassed != 0.0f || mDifferenceCheckState.mStage != DifferenceCheckState::Stage::FirstStage)) - { - CompleteDifferenceCheckCycle(); - } - - for (std::unique_ptr& action : mMementoStack.GetAllStoredActions()) - { - action->mRequiresReserialization = true; - } - - return std::move(mMementoStack); + // Bind the asset to our editor + Editor::RegisterAssetEditorSystem(); + return MetaType{ MetaType::T>{}, Format("AssetEditorSystem<{}>", MakeTypeName()), MetaType::Base{} }; } template - std::string AssetEditorSystem::Reserialize(std::string_view serialized) - { - std::optional loadInfo = AssetLoadInfo::LoadFromStream(std::make_unique(serialized)); - - if (!loadInfo.has_value()) - { - LOG(LogEditor, Error, "Failed to load metadata, metadata was invalid somehow?"); - return {}; - } - - T deserialized{ *loadInfo }; - AssetSaveInfo reserialized = deserialized.Save(); - return reserialized.ToString(); - } - - inline std::string Internal::GetSystemNameBasedOnAssetName(const std::string_view assetName) - { - return std::string{ assetName }; - } - - inline std::string Internal::GetAssetNameBasedOnSystemName(const std::string_view systemName) + std::unique_ptr> AssetEditorSystem::ConstructAsset(AssetLoadInfo& loadInfo) { - return std::string{ systemName }; + return MakeUniqueInPlace(loadInfo); } } diff --git a/Include/EditorSystems/AssetEditorSystems/ScriptEditorSystem.h b/Include/EditorSystems/AssetEditorSystems/ScriptEditorSystem.h index 6ce5c2d2..a90f6cef 100644 --- a/Include/EditorSystems/AssetEditorSystems/ScriptEditorSystem.h +++ b/Include/EditorSystems/AssetEditorSystems/ScriptEditorSystem.h @@ -1,3 +1,4 @@ +#include "Core/Input.h" #ifdef EDITOR #pragma once #include "EditorSystems/AssetEditorSystems/AssetEditorSystem.h" diff --git a/Include/EditorSystems/EditorSystem.h b/Include/EditorSystems/EditorSystem.h index cca54a73..f893ab0f 100644 --- a/Include/EditorSystems/EditorSystem.h +++ b/Include/EditorSystems/EditorSystem.h @@ -57,12 +57,7 @@ namespace CE */ virtual bool Begin(ImGuiWindowFlags flags = {}); - /* - For consistency, if our Begin() is a call to a field function, it makes sense if - our call to End() is also a field function, although as you can see the behaviour - does not differ from ImGui::End(). - */ - void End() const { ImGui::End(); } + virtual void End() { ImGui::End(); } private: friend ReflectAccess; diff --git a/Include/EditorSystems/ImporterSystem.h b/Include/EditorSystems/ImporterSystem.h index 1f8c7114..1c975f4f 100644 --- a/Include/EditorSystems/ImporterSystem.h +++ b/Include/EditorSystems/ImporterSystem.h @@ -84,7 +84,7 @@ namespace CE uint32 ShowDuplicateAssetsErrors(); - uint32 ShowErrorsToWarnAboutDiscardChanges(); + uint32 ShowErrorsToWarnAboutOverwritingChanges(); uint32 ShowReadOnlyErrors(); @@ -112,6 +112,7 @@ namespace CE static inline bool sExcludeDuplicates{}; static inline bool sIgnoreReadOnly = true; + static inline bool sOverWriteChanges{}; friend ReflectAccess; static MetaType Reflect(); diff --git a/Include/Utilities/DoUndo.h b/Include/Utilities/DoUndo.h deleted file mode 100644 index ba81e5fb..00000000 --- a/Include/Utilities/DoUndo.h +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once -#include -#include - -namespace CE -{ - namespace DoUndo - { - class Action - { - public: - virtual ~Action() = default; - - virtual void Do() = 0; - virtual void Undo() = 0; - }; - - template - class DoUndoStackBase - { - public: - DoUndoStackBase() = default; - - DoUndoStackBase(DoUndoStackBase&&) noexcept = default; - DoUndoStackBase(const DoUndoStackBase&) = delete; - - DoUndoStackBase& operator=(DoUndoStackBase&&) noexcept = default; - DoUndoStackBase& operator=(const DoUndoStackBase&) = delete; - - template - ActionType& Do(Args&& ...args) - { - ClearRedo(); - - auto action = std::make_unique(std::forward(args)...); - ActionType& returnValue = *action; - - mActionsTaken.push_back(std::move(action)); - mNumOfActionsDone++; - - DoTopActionAgain(); - - LOG(LogEditor, Verbose, "Added {} to DoUndoStack", typeid(ActionType).name()); - - return returnValue; - } - - void DoTopActionAgain() - { - T* mostRecent = PeekTop(); - - if (mostRecent != nullptr) - { - mostRecent->Do(); - mTimeLastActionAdded = std::chrono::high_resolution_clock::now(); - } - } - - bool CanUndo() const { return PeekTop() != nullptr; } - bool CanRedo() const { return mNumOfActionsDone < mActionsTaken.size(); } - - void Undo() - { - T* mostRecent = PeekTop(); - - if (mostRecent != nullptr) - { - LOG(LogEditor, Verbose, "Undoing action"); - mNumOfActionsDone--; - mostRecent->Undo(); - } - } - - void Redo() - { - if (CanRedo()) - { - LOG(LogEditor, Verbose, "Redoing action"); - mNumOfActionsDone++; - DoTopActionAgain(); - } - } - - void Clear() - { - mActionsTaken.clear(); - mNumOfActionsDone = 0; - } - - void ClearRedo() - { - mActionsTaken.resize(mNumOfActionsDone); - } - - const T* PeekTop() const - { - if (mNumOfActionsDone == 0) - { - return nullptr; - } - ASSERT(mNumOfActionsDone <= mActionsTaken.size()); - - return mActionsTaken[mNumOfActionsDone - 1].get(); - } - - T* PeekTop() - { - return const_cast(const_cast*>(this)->PeekTop()); - } - - static inline constexpr float sJustNowTreshold = 1.0f; - - float NumOfSecondsSinceLastActionAdded() const - { - const auto now = std::chrono::high_resolution_clock::now(); - return (std::chrono::duration_cast>(now - mTimeLastActionAdded)).count(); - } - - T* WhatDidWeJustDo() - { - T* top = PeekTop(); - - if (top != nullptr - && NumOfSecondsSinceLastActionAdded() <= sJustNowTreshold) - { - return top; - } - return nullptr; - } - - size_t GetNumOfActionsDone() const { return mNumOfActionsDone; } - - // Some of these actions may have been undone already! - std::span> GetAllStoredActions() { return mActionsTaken; } - std::span> GetAllStoredActions() const { return mActionsTaken; } - - private: - std::vector> mActionsTaken{}; - std::chrono::high_resolution_clock::time_point mTimeLastActionAdded{}; - size_t mNumOfActionsDone{}; - }; - - using DoUndoStack = DoUndoStackBase; - } -} \ No newline at end of file diff --git a/Include/Utilities/Reflect/ReflectFieldType.h b/Include/Utilities/Reflect/ReflectFieldType.h index cdd002b6..3ffce39c 100644 --- a/Include/Utilities/Reflect/ReflectFieldType.h +++ b/Include/Utilities/Reflect/ReflectFieldType.h @@ -23,7 +23,7 @@ namespace CE return; } - MetaProps& equalFuncProps = type.AddFunc(std::equal_to(), OperatorType::equal).GetProperties(); + MetaProps& equalFuncProps = type.AddFunc([](const T& lhs, const T& rhs) { return lhs == rhs; }, OperatorType::equal).GetProperties(); if (type.GetProperties().Has(Props::sIsScriptableTag)) { diff --git a/Source/Core/Editor.cpp b/Source/Core/Editor.cpp index 548d893e..de254055 100644 --- a/Source/Core/Editor.cpp +++ b/Source/Core/Editor.cpp @@ -15,7 +15,6 @@ #include "Meta/MetaProps.h" #include "EditorSystems/AssetEditorSystems/AssetEditorSystem.h" #include "Utilities/view_istream.h" -#include "GSON/GSONBinary.h" #include "Utilities/DrawDebugHelpers.h" #include "Utilities/NameLookUp.h" @@ -328,7 +327,7 @@ void CE::Editor::FullFillRefreshRequests() std::string mNameOfSystem{}; // Empty if this was not an asset editor, - std::optional mAssetEditorRestoreData{}; + std::optional mAssetEditorRestoreData{}; }; std::vector restorationData{}; @@ -366,7 +365,7 @@ system->GetName()); std::ostringstream savedStateStream{}; system->SaveState(savedStateStream); - if (AssetEditorSystemInterface* assetEditor = dynamic_cast(system.get()); + if (AssetEditorSystemBase* assetEditor = dynamic_cast(system.get()); assetEditor != nullptr) { if (combinedFlags & RefreshRequest::SaveAssetsToFile) @@ -374,7 +373,7 @@ system->GetName()); assetEditor->SaveToFile(); } - restoreInfo.mAssetEditorRestoreData = assetEditor->ExtractMementoStack(); + restoreInfo.mAssetEditorRestoreData = assetEditor->ExtractStack(); } DestroySystem(system->GetName()); @@ -418,10 +417,15 @@ system->GetName()); EditorSystem* system{}; if (restoreData.mAssetEditorRestoreData.has_value()) { - AssetEditorSystemInterface::MementoStack& stack = *restoreData.mAssetEditorRestoreData; + AssetEditorMementoStack& stack = *restoreData.mAssetEditorRestoreData; - const AssetEditorSystemInterface::MementoAction* topAction = stack.PeekTop(); - ASSERT(topAction != nullptr); + std::shared_ptr topAction = stack.GetMostRecentState(); + + if (topAction == nullptr) + { + LOG(LogEditor, Error, "Failed to restore to asset editor, topAction was nullptr"); + continue; + } std::optional loadInfo = AssetLoadInfo::LoadFromStream(std::make_unique(topAction->mState)); @@ -438,11 +442,11 @@ system->GetName()); if (system != nullptr && system->GetName() == restoreData.mNameOfSystem) { - AssetEditorSystemInterface* systemAsAssetEditor = dynamic_cast(system); + AssetEditorSystemBase* systemAsAssetEditor = dynamic_cast(system); if (systemAsAssetEditor != nullptr) { - systemAsAssetEditor->SetMementoStack(std::move(stack)); + systemAsAssetEditor->InsertMementoStack(std::move(stack)); } else { diff --git a/Source/Core/Engine.cpp b/Source/Core/Engine.cpp index 217ff4df..c75dc652 100644 --- a/Source/Core/Engine.cpp +++ b/Source/Core/Engine.cpp @@ -176,23 +176,21 @@ void CE::Engine::Run([[maybe_unused]] Name starterLevel) device.NewFrame(); input.NewFrame(); - if (device.GetDisplaySize().x <= 0 - || device.GetDisplaySize().y <= 0) + if (device.GetDisplaySize().x > 0 + && device.GetDisplaySize().y > 0) { - continue; - } - -#ifdef EDITOR - editor.Tick(deltaTime); -#else - world->Tick(deltaTime); + #ifdef EDITOR + editor.Tick(deltaTime); + #else + world->Tick(deltaTime); - if (world->HasRequestedEndPlay()) - { - break; + if (world->HasRequestedEndPlay()) + { + break; + } + world->Render(Device::Get().GetWindowPosition()); + #endif // EDITOR } - world->Render(Device::Get().GetWindowPosition()); -#endif // EDITOR renderer.RunCommandQueues(); device.EndFrame(); diff --git a/Source/EditorSystems/AssetEditorSystems/AssetEditorSystem.cpp b/Source/EditorSystems/AssetEditorSystems/AssetEditorSystem.cpp new file mode 100644 index 00000000..b0ff89ed --- /dev/null +++ b/Source/EditorSystems/AssetEditorSystems/AssetEditorSystem.cpp @@ -0,0 +1,361 @@ +#include "Precomp.h" +#include "EditorSystems/AssetEditorSystems/AssetEditorSystem.h" + +#include "Assets/Core/AssetLoadInfo.h" +#include "Core/Input.h" +#include "Core/ThreadPool.h" + +void CE::AssetEditorMementoStack::Do(std::shared_ptr action) +{ + ClearRedo(); + + mActions.push_back(std::move(action)); + mNumOfActionsDone++; +} + +bool CE::AssetEditorMementoStack::TryUndo() +{ + const bool canUndo = mNumOfActionsDone > 1; + mNumOfActionsDone -= canUndo; + return canUndo; +} + +bool CE::AssetEditorMementoStack::TryRedo() +{ + const bool canRedo = mNumOfActionsDone < mActions.size(); + mNumOfActionsDone += canRedo; + return canRedo; +} + +void CE::AssetEditorMementoStack::ClearRedo() +{ + mActions.resize(mNumOfActionsDone); +} + +std::shared_ptr CE::AssetEditorMementoStack::GetMostRecentState() const +{ + if (mNumOfActionsDone == 0) + { + return nullptr; + } + ASSERT(mNumOfActionsDone <= mActions.size()); + + return mActions[mNumOfActionsDone - 1]; +} + +CE::AssetEditorSystemBase::AssetEditorSystemBase(const Asset& asset) : // Don't hold onto this ref, it'll get invalidated + EditorSystem(Internal::GetSystemNameBasedOnAssetName(asset.GetName())) +{ +} + +CE::AssetEditorSystemBase::~AssetEditorSystemBase() +{ + if (mActionToAdd.valid()) + { + mActionToAdd.get(); + } +} + +CE::WeakAssetHandle CE::AssetEditorSystemBase::TryGetOriginalAsset() const +{ + return AssetManager::Get().TryGetWeakAsset(GetAsset().GetName()); +} + +std::optional CE::AssetEditorSystemBase::GetDestinationFile() const +{ + const WeakAssetHandle original = TryGetOriginalAsset(); + + if (original == nullptr) + { + return std::nullopt; + } + return original.GetFileOfOrigin(); +} + +void CE::AssetEditorSystemBase::SaveToFile() +{ + const std::optional dest = GetDestinationFile(); + + if (!dest.has_value()) + { + LOG(LogEditor, Error, "Could not save asset {} - no destination file path", + GetAsset().GetName()); + return; + } + + Editor::Get().Refresh( + { + Editor::RefreshRequest::Volatile, + + // A shared pointer, because it makes move semantics with lambdas so much simpler + [assetSaveInfo = std::make_shared(SaveToMemory()), saveTo = *dest] + { + assetSaveInfo->SaveToFile(saveTo); + } + }); +} + +CE::AssetSaveInfo CE::AssetEditorSystemBase::SaveToMemory() +{ + ApplyChangesToAsset(); + + std::optional importerInfo{}; + + if (const WeakAssetHandle originalAsset = TryGetOriginalAsset(); originalAsset != nullptr) + { + importerInfo = originalAsset.GetMetaData().GetImporterInfo(); + + if (importerInfo.has_value()) + { + importerInfo->mWereEditsMadeAfterImporting |= !IsSavedToFile(); + } + } + + AssetSaveInfo saveInfo = GetAsset().Save(std::move(importerInfo)); + return saveInfo; +} + +bool CE::AssetEditorSystemBase::IsSavedToFile() const +{ + const std::shared_ptr mostRecentState = mMementoStack.GetMostRecentState(); + + if (mostRecentState == nullptr) + { + return true; + } + return mostRecentState->mSimilarityToFile.mDoesStateMatchFile; +} + +CE::AssetEditorMementoStack CE::AssetEditorSystemBase::ExtractStack() +{ + if (mActionToAdd.valid()) + { + mActionToAdd.get(); + CheckForDifferences(); + } + + return std::move(mMementoStack); +} + +void CE::AssetEditorSystemBase::InsertMementoStack(AssetEditorMementoStack stack) +{ + mMementoStack = std::move(stack); + + for (const std::shared_ptr& action : mMementoStack.mActions) + { + // Our asset might have depended on other assets + // that have now been renamed, deleted, or otherwise + // altered. + action->mRequiresReserialization = true; + } +} + +bool CE::AssetEditorSystemBase::Begin(ImGuiWindowFlags flags) +{ + const bool isWindowOpen = EditorSystem::Begin(flags | (IsSavedToFile() ? 0 : ImGuiWindowFlags_UnsavedDocument)); + mAssetMutex.lock(); + + if (isWindowOpen) + { + if (!Input::Get().HasFocus()) + { + return isWindowOpen; + } + + const auto requestApplyStateChange = [this] + { + // The refreshing will return the asset editor to the + // top state in the stack + Editor::Get().Refresh({ 0, {}, GetName() }); + }; + + if (Input::Get().IsKeyboardKeyHeld(Input::KeyboardKey::LeftControl) + || Input::Get().IsKeyboardKeyHeld(Input::KeyboardKey::RightControl)) + { + if (Input::Get().WasKeyboardKeyPressed(Input::KeyboardKey::Z) + && mMementoStack.TryUndo()) + { + requestApplyStateChange(); + return isWindowOpen; + } + + if (Input::Get().WasKeyboardKeyPressed(Input::KeyboardKey::Y) + && mMementoStack.TryRedo()) + { + requestApplyStateChange(); + return isWindowOpen; + } + + if (Input::Get().WasKeyboardKeyPressed(Input::KeyboardKey::S)) + { + SaveToFile(); + } + } + + CheckForDifferences(); + } + + return isWindowOpen; +} + +void CE::AssetEditorSystemBase::End() +{ + mAssetMutex.unlock(); + EditorSystem::End(); +} + +void CE::AssetEditorSystemBase::ShowSaveButton() +{ + if (ImGui::Button(ICON_FA_FLOPPY_O)) + { + SaveToFile(); + } +} + +CE::MetaType CE::AssetEditorSystemBase::Reflect() +{ + return { MetaType::T{}, "AssetEditorSystemBase", MetaType::Base{} }; +} + +void CE::AssetEditorSystemBase::CheckForDifferences() +{ + if (mActionToAdd.valid()) + { + if (!IsFutureReady(mActionToAdd)) + { + return; + } + + std::optional change = mActionToAdd.get(); + + if (change.has_value()) + { + mMementoStack.Do(std::make_shared(std::move(*change))); + } + + mDifferenceCheckTimer.Reset(); + } + + if (mDifferenceCheckTimer.GetSecondsElapsed() < sDifferenceCheckCooldown) + { + return; + } + + const auto updateIsSameAsFile = [this](AssetEditorMementoStack::Action& action) + { + const std::optional destinationPath = GetDestinationFile(); + + if (!destinationPath.has_value() + || !std::filesystem::exists(*destinationPath)) + { + action.mSimilarityToFile = {}; + return; + } + + const std::filesystem::file_time_type destinationWriteTime = std::filesystem::last_write_time(*destinationPath); + + if (mCachedFile.mLastWriteTime != destinationWriteTime) + { + mCachedFile.mLastWriteTime = destinationWriteTime; + + std::optional loadInfo = AssetLoadInfo::LoadFromFile(*destinationPath); + + if (!loadInfo.has_value()) + { + LOG(LogEditor, Error, "Could not load asset from file {}", destinationPath->string()); + action.mSimilarityToFile = {}; + return; + } + + const auto assetPtr = ConstructAsset(*loadInfo); + mCachedFile.mFileContents = Reserialize(assetPtr->Save().ToString()); + } + + if (action.mSimilarityToFile.mFileWriteTimeLastTimeWeChecked == mCachedFile.mLastWriteTime) + { + return; + } + + if (action.mRequiresReserialization) + { + action.mState = Reserialize(action.mState); + action.mRequiresReserialization = false; + } + + action.mSimilarityToFile.mFileWriteTimeLastTimeWeChecked = mCachedFile.mLastWriteTime; + action.mSimilarityToFile.mDoesStateMatchFile = action.mState == mCachedFile.mFileContents; + }; + + mActionToAdd = ThreadPool::Get().Enqueue( + [this, + topAction = mMementoStack.GetMostRecentState(), + currentState = SaveToMemory().ToString(), + updateIsSameAsFile]() -> std::optional + { + if (topAction != nullptr) + { + updateIsSameAsFile(*topAction); + } + + AssetEditorMementoStack::Action action{}; + action.mState = [this] + { + std::unique_lock lock{ mAssetMutex }; + return SaveToMemory().ToString(); + }(); + + // Exactlyy the same as before, no changes were made + if (topAction != nullptr + && action.mState == topAction->mState) + { + return std::nullopt; + } + + action.mState = Reserialize(action.mState); + + if (topAction == nullptr) + { + updateIsSameAsFile(action); + return action; + } + + if (topAction->mRequiresReserialization) + { + topAction->mState = Reserialize(topAction->mState); + topAction->mRequiresReserialization = false; + } + + if (topAction->mState != action.mState) + { + LOG(LogEditor, Verbose, "Change detected for {}", GetName()); + updateIsSameAsFile(action); + return action; + } + return std::nullopt; + }); +} + +std::string CE::AssetEditorSystemBase::Reserialize(std::string_view serialized) +{ + std::optional loadInfo = AssetLoadInfo::LoadFromStream(std::make_unique(serialized)); + + if (!loadInfo.has_value()) + { + LOG(LogEditor, Error, "Failed to load metadata, metadata was invalid somehow?"); + return {}; + } + + const auto assetPtr = ConstructAsset(*loadInfo); + const AssetSaveInfo reserialized = assetPtr->Save(); + return reserialized.ToString(); +} + +std::string CE::Internal::GetSystemNameBasedOnAssetName(const std::string_view assetName) +{ + return std::string{ assetName }; +} + +std::string CE::Internal::GetAssetNameBasedOnSystemName(const std::string_view systemName) +{ + return std::string{ systemName }; +} \ No newline at end of file diff --git a/Source/EditorSystems/AssetEditorSystems/LevelEditorSystem.cpp b/Source/EditorSystems/AssetEditorSystems/LevelEditorSystem.cpp index 3a3cb89a..67f83d97 100644 --- a/Source/EditorSystems/AssetEditorSystems/LevelEditorSystem.cpp +++ b/Source/EditorSystems/AssetEditorSystems/LevelEditorSystem.cpp @@ -20,8 +20,6 @@ void CE::LevelEditorSystem::Tick(const float deltaTime) return; } - AssetEditorSystem::Tick(deltaTime); - if (ImGui::BeginMenuBar()) { ShowSaveButton(); diff --git a/Source/EditorSystems/AssetEditorSystems/MaterialEditorSystem.cpp b/Source/EditorSystems/AssetEditorSystems/MaterialEditorSystem.cpp index 5da34672..55735ca6 100644 --- a/Source/EditorSystems/AssetEditorSystems/MaterialEditorSystem.cpp +++ b/Source/EditorSystems/AssetEditorSystems/MaterialEditorSystem.cpp @@ -11,7 +11,7 @@ CE::MaterialEditorSystem::MaterialEditorSystem(Material&& asset) : CE::MaterialEditorSystem::~MaterialEditorSystem() = default; -void CE::MaterialEditorSystem::Tick(const float deltaTime) +void CE::MaterialEditorSystem::Tick([[maybe_unused]] const float deltaTime) { if (!Begin(ImGuiWindowFlags_MenuBar)) { @@ -19,8 +19,6 @@ void CE::MaterialEditorSystem::Tick(const float deltaTime) return; } - AssetEditorSystem::Tick(deltaTime); - if (ImGui::BeginMenuBar()) { ShowSaveButton(); diff --git a/Source/EditorSystems/AssetEditorSystems/PrefabEditorSystem.cpp b/Source/EditorSystems/AssetEditorSystems/PrefabEditorSystem.cpp index fcd641bd..ef10b951 100644 --- a/Source/EditorSystems/AssetEditorSystems/PrefabEditorSystem.cpp +++ b/Source/EditorSystems/AssetEditorSystems/PrefabEditorSystem.cpp @@ -57,8 +57,6 @@ void CE::PrefabEditorSystem::Tick(const float deltaTime) return; } - AssetEditorSystem::Tick(deltaTime); - if (ImGui::BeginMenuBar()) { ShowSaveButton(); diff --git a/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptClassPanel.cpp b/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptClassPanel.cpp index 954b6852..2ad0969f 100644 --- a/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptClassPanel.cpp +++ b/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptClassPanel.cpp @@ -5,6 +5,7 @@ // it was all in one file. #include "EditorSystems/AssetEditorSystems/ScriptEditorSystem.h" #include "Utilities/Events.h" +#include "Utilities/StringFunctions.h" #include "Utilities/Imgui/ImguiHelpers.h" diff --git a/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptEditorSystem.cpp b/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptEditorSystem.cpp index 8770314a..f6f0c972 100644 --- a/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptEditorSystem.cpp +++ b/Source/EditorSystems/AssetEditorSystems/ScriptEditorSystem/ScriptEditorSystem.cpp @@ -29,7 +29,7 @@ CE::ScriptEditorSystem::~ScriptEditorSystem() } } -void CE::ScriptEditorSystem::Tick(const float deltaTime) +void CE::ScriptEditorSystem::Tick([[maybe_unused]] const float deltaTime) { if (!Begin(ImGuiWindowFlags_MenuBar)) { @@ -37,7 +37,6 @@ void CE::ScriptEditorSystem::Tick(const float deltaTime) return; } - AssetEditorSystem::Tick(deltaTime); ax::NodeEditor::SetCurrentEditor(mContext); if (ImGui::BeginMenuBar()) diff --git a/Source/EditorSystems/ImporterSystem.cpp b/Source/EditorSystems/ImporterSystem.cpp index 757bc1e9..5aaf8243 100644 --- a/Source/EditorSystems/ImporterSystem.cpp +++ b/Source/EditorSystems/ImporterSystem.cpp @@ -168,11 +168,12 @@ void CE::ImporterSystem::Tick(const float dt) Preview(); uint32 numOfConflicts = ShowDuplicateAssetsErrors(); - numOfConflicts += ShowErrorsToWarnAboutDiscardChanges(); + numOfConflicts += ShowErrorsToWarnAboutOverwritingChanges(); numOfConflicts += ShowReadOnlyErrors(); ImGui::Checkbox("Exclude duplicates", &sExcludeDuplicates); ImGui::Checkbox("Ignore read only", &sIgnoreReadOnly); + ImGui::Checkbox("Overwrite changes", &sOverWriteChanges); if (numOfConflicts != 0) { @@ -679,8 +680,13 @@ uint32 CE::ImporterSystem::ShowDuplicateAssetsErrors() return numOfErrors; } -uint32 CE::ImporterSystem::ShowErrorsToWarnAboutDiscardChanges() +uint32 CE::ImporterSystem::ShowErrorsToWarnAboutOverwritingChanges() { + if (sOverWriteChanges) + { + return 0; + } + bool isOpenAlready{}, shouldDisplay{}; uint32 numOfErrors{}; diff --git a/Source/World/Archiver.cpp b/Source/World/Archiver.cpp index 982980a6..e3c7467d 100644 --- a/Source/World/Archiver.cpp +++ b/Source/World/Archiver.cpp @@ -7,6 +7,7 @@ #include "Components/TransformComponent.h" #include "Assets/Prefabs/PrefabEntityFactory.h" #include "Components/PrefabOriginComponent.h" +#include "Core/ThreadPool.h" #include "Meta/MetaType.h" #include "Meta/MetaManager.h" #include "Meta/MetaFuncId.h" @@ -306,58 +307,75 @@ CE::BinaryGSONObject CE::Archiver::SerializeInternal(const World& world, std::ve BinaryGSONObject save{ "SerializedWorld" }; std::sort(entitiesToSerialize.begin(), entitiesToSerialize.end()); - save.AddGSONMember("entities") << entitiesToSerialize; - size_t numOfStorages{}; - for ([[maybe_unused]] auto _ : reg.Storage()) - { - ++numOfStorages; - } + const auto storageRange = reg.Storage(); + const size_t numOfStorages = std::distance(storageRange.begin(), storageRange.end()); + save.ReserveChildren(numOfStorages); + std::vector> storageFutures{}; + storageFutures.reserve(numOfStorages); + for (auto&& [typeId, storage] : reg.Storage()) { - if (storage.empty()) - { - continue; - } + BinaryGSONObject& serializedComponentClass = save.AddGSONObject({}); - const std::optional serializeArg = GetComponentClassSerializeArg(storage); + storageFutures.emplace_back(ThreadPool::Get().Enqueue( + [&] + { + if (storage.empty()) + { + return; + } - if (!serializeArg.has_value()) - { - continue; - } + const std::optional serializeArg = GetComponentClassSerializeArg(storage); + + if (!serializeArg.has_value()) + { + return; + } - BinaryGSONObject& serializedComponentClass = save.AddGSONObject(serializeArg->mComponentClass.GetName()); - serializedComponentClass.ReserveChildren(storage.size()); + serializedComponentClass.SetName(serializeArg->mComponentClass.GetName()); + serializedComponentClass.ReserveChildren(storage.size()); - for (const entt::entity entity : storage) - { - if (!allEntitiesInWorldAreBeingSerialized - && !std::binary_search(entitiesToSerialize.begin(), entitiesToSerialize.end(), entity)) - { - continue; - } + for (const entt::entity entity : storage) + { + if (!allEntitiesInWorldAreBeingSerialized + && !std::binary_search(entitiesToSerialize.begin(), entitiesToSerialize.end(), entity)) + { + continue; + } - SerializeSingleComponent(reg, - serializedComponentClass, - entity, - *serializeArg); - } + SerializeSingleComponent(reg, + serializedComponentClass, + entity, + *serializeArg); + } - // We want to guarantee that after deserializing this and then reserializing it, we get the same result. - // But entt::registry will jumble up the order of the entities for us. - // This ensures that we get the same order every time we save. - std::sort(serializedComponentClass.GetChildren().begin(), serializedComponentClass.GetChildren().end(), - [](const BinaryGSONObject& lhs, const BinaryGSONObject& rhs) - { - // Faster than string comparisons - return *reinterpret_cast(lhs.GetName().c_str()) < *reinterpret_cast(rhs.GetName().c_str()); - }); + // We want to guarantee that after deserializing this and then reserializing it, we get the same result. + // But entt::registry will jumble up the order of the entities for us. + // This ensures that we get the same order every time we save. + std::sort(serializedComponentClass.GetChildren().begin(), serializedComponentClass.GetChildren().end(), + [](const BinaryGSONObject& lhs, const BinaryGSONObject& rhs) + { + // Faster than string comparisons + return *reinterpret_cast(lhs.GetName().c_str()) < *reinterpret_cast(rhs.GetName().c_str()); + }); + })); + } + + for (const std::future& future : storageFutures) + { + future.wait(); } + std::erase_if(save.GetChildren(), + [](const BinaryGSONObject& obj) + { + return obj.GetName().empty(); + }); + return save; }