diff --git a/engine/include/cubos/engine/assets/assets.hpp b/engine/include/cubos/engine/assets/assets.hpp index e7b51f456..2ae62d550 100644 --- a/engine/include/cubos/engine/assets/assets.hpp +++ b/engine/include/cubos/engine/assets/assets.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ namespace cubos::engine Unloaded, ///< The asset is not loaded. Loading, ///< The asset is being loaded. Loaded, ///< The asset is loaded. + Failed, ///< The asset failed to load. }; ~Assets(); @@ -86,6 +88,11 @@ namespace cubos::engine /// @param path Path to load metadata from. void importAll(std::string_view path); + /// @brief Get AssetHandle from Path. + /// @param path Path to get handle from. + /// @return AssetHandle from Path, or a null handle if unable to find. + AnyAsset getAsset(std::string_view path); + /// @brief Loads all metadata from the virtual filesystem, in the given path. If the path /// points to a directory, it will be recursively searched for metadata files. /// @param path Path to load metadata from. @@ -152,6 +159,28 @@ namespace cubos::engine return AssetRead(*data, std::move(lock)); } + /// @brief Gets read-only access to the asset data associated with the given handle. + /// + /// If the asset is not loaded, this blocks until it is. If the asset cannot be loaded, + /// returns std::nullopt. + /// + /// @tparam T Type of the asset data. + /// @param handle Handle to get the asset data for. + /// @return Reference to the asset data. + template + inline std::optional> tryRead(Asset handle) const + { + // Create a strong handle to the asset, so that the asset starts loading if it isn't already. + auto strong = this->load(handle); + auto lock = this->lockRead(strong); + auto data = static_cast(this->tryAccess(strong, core::reflection::reflect(), lock, false)); + if (data == nullptr) + { + return std::nullopt; + } + return std::cref(*data); + } + /// @brief Gets read-write access to the asset data associated with the given handle. /// /// If the asset is not loaded, this blocks until it is. If the asset cannot be loaded, @@ -302,6 +331,21 @@ namespace cubos::engine template void* access(const AnyAsset& handle, const core::reflection::Type& type, Lock& lock, bool incVersion) const; + /// @brief Gets a pointer to the asset data associated with the given handle. + /// + /// If the asset is not loaded, this blocks until it is. If the asset cannot be loaded, + /// return nullpt. If this function is called from the loader thread, instead of waiting + /// for the asset to load, it will be loaded synchronously. + /// + /// @tparam Lock The type of the lock guard. + /// @param handle Handle to get the asset data for. + /// @param type Expected type of the asset data. + /// @param lock Lock guard for the asset. + /// @param incVersion Whether to increase the asset's version. + /// @return Pointer to the asset's data. + template + void* tryAccess(const AnyAsset& handle, const core::reflection::Type& type, Lock& lock, bool incVersion) const; + /// @brief Locks the given asset for reading. /// @param handle Handle to lock. /// @return Lock guard. diff --git a/engine/include/cubos/engine/voxels/voxel_model.hpp b/engine/include/cubos/engine/voxels/voxel_model.hpp index ed8da3430..a29225f7e 100644 --- a/engine/include/cubos/engine/voxels/voxel_model.hpp +++ b/engine/include/cubos/engine/voxels/voxel_model.hpp @@ -47,7 +47,11 @@ namespace cubos::engine const VoxelPalette& getPalette() const; - VoxelGrid& getGrid(uint16_t index); + void setPalette(const VoxelPalette& palette); + + const VoxelGrid& getGrid(uint16_t index) const; + + void setGrid(uint16_t index, const VoxelGrid& grid, const glm::ivec3& position); /// @brief Loads the grid's data from the given stream. /// diff --git a/engine/src/assets/assets.cpp b/engine/src/assets/assets.cpp index 5717bdcf3..b047dddc1 100644 --- a/engine/src/assets/assets.cpp +++ b/engine/src/assets/assets.cpp @@ -108,7 +108,7 @@ void Assets::importAll(std::string_view path) child = child->sibling(); } } - // Skip files that already have a .meta extension. + // Skip meta files. else if (!file->name().ends_with(".meta")) { // Check if the .meta file exists. @@ -141,6 +141,19 @@ void Assets::importAll(std::string_view path) } } +AnyAsset Assets::getAsset(std::string_view path) +{ + for (const auto& [uuid, entry] : mEntries) + { + auto path_meta = entry->meta.get("path"); + if (path_meta.has_value() && path_meta.value() == path) + { + return AnyAsset(uuid); + } + } + return {}; +} + void Assets::loadMeta(std::string_view path) { auto file = core::data::FileSystem::find(path); @@ -524,6 +537,66 @@ void* Assets::access(const AnyAsset& handle, const Type& type, Lock& lock, bool return assetEntry->data; } +template +void* Assets::tryAccess(const AnyAsset& handle, const Type& type, Lock& lock, bool incVersion) const +{ + CUBOS_ASSERT(!handle.isNull(), "Could not access asset"); + + // Get the entry for the asset. + auto assetEntry = this->entry(handle); + CUBOS_ASSERT(assetEntry != nullptr, "Could not access asset"); + + // If the asset hasn't been loaded yet, and its bridge isn't asynchronous, or we're in the loader thread, then load + // it now. + if (assetEntry->status != Status::Loaded) + { + auto bridge = this->bridge(handle); + CUBOS_ASSERT(bridge != nullptr, "Could not access asset"); + + if (!bridge->asynchronous() || std::this_thread::get_id() == mLoaderThread.get_id()) + { + CUBOS_DEBUG("Loading asset {} as a dependency", handle); + + // Load the asset - the const_cast is okay since the const qualifiers are only used to make + // the interface more readable. We need to unlock temporarily to avoid a deadlock, since the + // bridge will call back into the asset manager. + lock.unlock(); + if (!bridge->load(const_cast(*this), handle)) + { + CUBOS_CRITICAL("Could not load asset {}", handle); + abort(); + } + lock.lock(); + } + } + + // Wait until the asset finishes loading. + while (assetEntry->status == Status::Loading) + { + assetEntry->cond.wait(lock); + } + + if (assetEntry->status != Status::Loaded || assetEntry->data == nullptr) + { + CUBOS_ERROR("Could not access asset"); + return nullptr; + } + + if (assetEntry->type != &type) + { + CUBOS_ERROR("Type mismatch, expected {}, got {}", type.name(), assetEntry->type->name()); + return nullptr; + } + + if (incVersion) + { + assetEntry->version++; + CUBOS_DEBUG("Incremented version of asset {}", handle); + } + + return assetEntry->data; +} + // Define the explicit instantiations of the access() function, one for each lock type. // Necessary because the function is template - would otherwise cause linker errors. @@ -531,6 +604,10 @@ template void* Assets::access>(const AnyAsse std::shared_lock&, bool) const; template void* Assets::access>(const AnyAsset&, const Type&, std::unique_lock&, bool) const; +template void* Assets::tryAccess>(const AnyAsset&, const Type&, + std::shared_lock&, bool) const; +template void* Assets::tryAccess>(const AnyAsset&, const Type&, + std::unique_lock&, bool) const; std::shared_lock Assets::lockRead(const AnyAsset& handle) const { @@ -680,7 +757,7 @@ void Assets::loader() auto assetEntry = this->entry(task.handle); CUBOS_ASSERT(assetEntry != nullptr, "This should never happen"); - assetEntry->status = Assets::Status::Unloaded; + assetEntry->status = Assets::Status::Failed; assetEntry->cond.notify_all(); } else diff --git a/engine/src/voxels/voxel_model.cpp b/engine/src/voxels/voxel_model.cpp index e45e49c6a..fa73ecc9e 100644 --- a/engine/src/voxels/voxel_model.cpp +++ b/engine/src/voxels/voxel_model.cpp @@ -33,11 +33,26 @@ const VoxelPalette& VoxelModel::getPalette() const return palette; } -VoxelGrid& VoxelModel::getGrid(uint16_t index) +void VoxelModel::setPalette(const VoxelPalette& palette) +{ + this->palette = palette; +} + +const VoxelGrid& VoxelModel::getGrid(uint16_t index) const { return matrices[index].grid; } +void VoxelModel::setGrid(uint16_t index, const VoxelGrid& grid, const glm::ivec3& position) +{ + if (index >= matrices.size()) + { + matrices.resize(index + 1); + } + matrices[index].grid = grid; + matrices[index].position = position; +} + bool VoxelModel::loadFrom(Stream& stream) { uint8_t version[4] = {0, 0, 0, 0}; diff --git a/tools/tesseratos/src/tesseratos/importer/plugin.cpp b/tools/tesseratos/src/tesseratos/importer/plugin.cpp index be20d99f0..320918807 100644 --- a/tools/tesseratos/src/tesseratos/importer/plugin.cpp +++ b/tools/tesseratos/src/tesseratos/importer/plugin.cpp @@ -18,6 +18,7 @@ using cubos::core::data::FileSystem; using cubos::core::ecs::EventReader; using cubos::engine::AnyAsset; +using cubos::engine::AssetRead; using cubos::engine::Assets; using cubos::engine::Cubos; using cubos::engine::VoxelGrid; @@ -33,174 +34,157 @@ namespace { struct ImportState { + // Generic AnyAsset currentAsset; - int gridCount = 1; - std::unordered_map gridFilePaths; - char paletteFilePath[256] = ""; + std::string currentAssetPath = ""; bool showError = false; - }; -} // namespace - -/// Tries to load a VoxelModel from the given path. -/// @param path The path of the VoxelModel. -/// @param model The model to fill. -static bool loadVoxelModel(const fs::path& path, VoxelModel& model) -{ - std::string fullPath = "../../tools/tesseratos" + path.string(); - auto* file = fopen(fullPath.c_str(), "rb"); - if (file == nullptr) - { - return false; - } - - auto stream = memory::StandardStream(file, true); - if (!model.loadFrom(stream)) - { - CUBOS_ERROR("Failed to deserialize model."); - return false; - } - return true; -} - -/// Tries to load the palette from the given path. -/// @param path The path of the palette. -/// @param palette The palette to fill. -static bool loadPalette(const fs::path& path, VoxelPalette& palette) -{ - std::string fullPath = "../../tools/tesseratos" + path.string(); - auto* file = fopen(fullPath.c_str(), "rb"); - if (file == nullptr) - { - return false; - } - - auto stream = memory::StandardStream(file, true); - if (!palette.loadFrom(stream)) - { - CUBOS_ERROR("Failed to deserialize palette."); - return false; - } - - return true; -} - -/// Saves the given palette to the given path. -/// @param path The path of the palette. -/// @param palette The palette to save. -static bool savePalette(const fs::path& path, const VoxelPalette& palette) -{ - std::string fullPath = "../../tools/tesseratos" + path.string(); - auto* file = fopen(fullPath.c_str(), "wb"); - if (file == nullptr) - { - return false; - } - - auto stream = memory::StandardStream(file, true); - if (!palette.writeTo(stream)) - { - CUBOS_ERROR("Failed to serialize palette."); - return false; - } - - return true; -} - -/// Saves the given grid to the given path. -/// @param path The path of the grid. -/// @param grid The grid to export. -static bool saveGrid(const fs::path& path, const VoxelGrid& grid) -{ - std::string fullPath = "../../tools/tesseratos" + path.string(); - auto* file = fopen(fullPath.c_str(), "wb"); - if (file == nullptr) - { - return false; - } + bool isModelLoaded = false; - auto stream = memory::StandardStream(file, true); - if (!grid.writeTo(stream)) - { - CUBOS_ERROR("Failed to serialize grid."); - return false; - } + // VoxelModel specifics + int gridMax = 0; + int gridCount = 0; + std::unordered_map gridFilePaths; + std::string paletteFilePath = ""; - return true; -} + // Other Imports... + }; +} // namespace -static bool handleImport(ImportState& state, VoxelModel& model) +static bool handleImport(Assets& assets, ImportState& state) { - // First, load the palette. - VoxelPalette palette; - if (strlen(state.paletteFilePath) > 0) + CUBOS_INFO("Starting handleImport"); + auto paletteHandle = assets.getAsset(state.paletteFilePath); { - if (!loadPalette(state.paletteFilePath, palette)) + auto model = assets.read(state.currentAsset); + if (paletteHandle.isNull()) { - CUBOS_WARN("Failed to load palette: write disabled & file {} not found. Going to create new one", - state.paletteFilePath); - } - } - - // Iterate over the grids which will be exported. - for (uint16_t i = 0; i < model.getMatricesSize(); ++i) - { - if (state.gridFilePaths.contains(i)) - { - auto modelsPalette = model.getPalette(); - auto modelsGrid = model.getGrid(i); - auto path = state.gridFilePaths.at(i); - std::string fullPath = "../../tools/tesseratos" + path; - if (std::filesystem::exists(fullPath)) - { - CUBOS_WARN("Output file already exists re-Importing."); - } - - // If write is enabled, merge this palette with the main one. - palette.merge(modelsPalette, 1.0F); - - // Convert the grid to the main palette. - if (!modelsGrid.convert(modelsPalette, palette, 1.0F)) + CUBOS_INFO("Failed to get asset handle for paletteFilePath: {}", state.paletteFilePath); + CUBOS_INFO("Creating new palette asset."); + // Should create new palette asset. + paletteHandle = assets.create(VoxelPalette{}); + assets.writeMeta(paletteHandle)->set("path", state.paletteFilePath); + assets.save(paletteHandle); + if (paletteHandle.isNull()) { - CUBOS_ERROR("Error converting grid to main palette"); + // Should not happen. + CUBOS_ERROR("Failed to create palette asset."); return false; } - - // Save the grid to the given path. - if (!saveGrid(path, modelsGrid)) + } + { + auto palette = assets.write(paletteHandle); + // Iterate over the grids which will be exported. + for (uint16_t i = 0; i < model.get().getMatricesSize(); ++i) { - CUBOS_ERROR("Failed to save grid {} to {} .", i, path); - return false; + if (state.gridFilePaths.contains(i)) + { + auto gridPath = state.gridFilePaths.at(i); + auto gridHandle = assets.getAsset(gridPath); + if (gridHandle.isNull()) + { + CUBOS_INFO("Failed to get asset handle for gridPath: {}", gridPath); + CUBOS_INFO("Creating new grid asset."); + gridHandle = assets.create(VoxelGrid{}); + assets.writeMeta(gridHandle)->set("path", gridPath); + assets.save(gridHandle); + if (gridHandle.isNull()) + { + // Should not happen. + CUBOS_ERROR("Failed to create grid asset."); + return false; + } + } + // Merge this palette with the main one. + palette.get().merge(model.get().getPalette(), 1.0F); + + { + auto grid = assets.write(gridHandle); + // Convert the grid to the main palette. + if (!grid.get().convert(model.get().getPalette(), palette.get(), 1.0F)) + { + CUBOS_ERROR("Error converting grid to main palette"); + return false; + } + } + + CUBOS_INFO("Saving grid to path: {}", gridPath); + + // Save the grid to the given path. + if (!assets.save(gridHandle)) + { + CUBOS_ERROR("Error saving grid to path: {}", gridPath); + return false; + } + + // CUBOS_INFO("Setting grid path to asset meta"); + // Not sure if needed (would be helpful probably). Causes deadlock for now. + // assets.writeMeta(state.currentAsset)->set("grid_path", gridPath); + } } } } - if (!savePalette(state.paletteFilePath, palette)) + CUBOS_INFO("Saving palette to path: {}", state.paletteFilePath); + + // Save the palette to the asset system. + if (!assets.save(paletteHandle)) + { + CUBOS_ERROR("Error saving palette to asset system"); + return false; + } + + // CUBOS_INFO("Setting palette path to asset meta"); + // Not sure if needed (would be helpful probably). + // assets.writeMeta(state.currentAsset)->set("palette_path", state.paletteFilePath); + + // Save the model to the asset system. + if (!assets.save(state.currentAsset)) { - CUBOS_ERROR("Failed to save palette to {} .", state.paletteFilePath); + CUBOS_ERROR("Error saving model to asset system"); return false; } + CUBOS_INFO("handleImport completed successfully"); return true; } static void showImport(Assets& assets, cubos::core::ecs::EventReader reader, ImportState& state) { + // The instance where the asset has changed. + bool assetChanged = false; for (const auto& event : reader) { - state.currentAsset = event.asset; + if (event.asset != state.currentAsset) + { + state.currentAsset = event.asset; + assetChanged = true; + state.isModelLoaded = false; + } } - if (state.currentAsset == nullptr) { ImGui::Text("No asset selected"); + state.isModelLoaded = false; + return; } else if (assets.type(state.currentAsset).is()) { - VoxelModel model; - if (!loadVoxelModel(fs::path(assets.readMeta(state.currentAsset)->get("path").value()), model)) + std::optional> voxelModelResult; + if (assetChanged) + { + voxelModelResult = assets.tryRead(state.currentAsset); + } + if (!state.isModelLoaded || + assets.readMeta(state.currentAsset)->get("path").value_or("") != state.currentAssetPath) { - ImGui::TextColored({1.0F, 0.0F, 0.0F, 1.0F}, "Failed to load VoxelModel."); - return; + if (!voxelModelResult.has_value()) + { + ImGui::Text("Error reading VoxelModel check console for details"); + return; + } + state.gridMax = voxelModelResult.value().get().getMatricesSize(); + state.currentAssetPath = assets.readMeta(state.currentAsset)->get("path").value_or(""); + state.isModelLoaded = true; } // Handle VoxelModel import options @@ -208,21 +192,26 @@ static void showImport(Assets& assets, cubos::core::ecs::EventReaderget("path").value_or("Unknown").c_str()); ImGui::Text("Palette File Path:"); - ImGui::InputTextWithHint("##paletteFilePath", "Ex:/assets/models/main.pal", state.paletteFilePath, - IM_ARRAYSIZE(state.paletteFilePath)); + char paletteBuffer[256] = ""; + strncpy(paletteBuffer, state.paletteFilePath.c_str(), sizeof(paletteBuffer)); + if (ImGui::InputTextWithHint("##paletteFilePath", "Ex:/assets/models/main.pal", paletteBuffer, + sizeof(paletteBuffer))) + { + state.paletteFilePath = paletteBuffer; + } ImGui::Spacing(); - ImGui::Text("Number of Grids: Max Supported by file: %d", model.getMatricesSize()); + ImGui::Text("Number of Grids: Max Supported by file: %d", state.gridMax); ImGui::InputInt("##gridCount", &state.gridCount, 1, 5); if (state.gridCount < 1) { state.gridCount = 1; } - else if (state.gridCount > model.getMatricesSize()) + else if (state.gridCount > state.gridMax) { - state.gridCount = model.getMatricesSize(); + state.gridCount = state.gridMax; } for (int i = 0; i < state.gridCount; ++i) @@ -262,7 +251,7 @@ static void showImport(Assets& assets, cubos::core::ecs::EventReaderget("path").value_or("Unknown").c_str()); }