/****************************************************************************** * File - content_manager.cpp * Author - Joey Pollack * Date - 2022/02/22 (y/m/d) * Mod Date - 2022/02/22 (y/m/d) * Description - Keeps track of all resource files in the project. * Reads/Writes meta-data to the contents_meta.xml file. * Also manages the physical location of each asset file. ******************************************************************************/ #include "content_manager.h" #include #include "editor_asset.h" #include "../project.h" #include #include #include // Asset types #include "tile_set.h" #include "world.h" namespace lunarium { namespace editor { ContentManager* ContentManager::mpInstance = nullptr; ContentManager::ContentManager() : mpProject(nullptr), mNextID(0) { } ContentManager& ContentManager::GetInstance() { if (!mpInstance) { mpInstance = new ContentManager(); } return *mpInstance; } void ContentManager::FreeInstance() { if (mpInstance) { delete mpInstance; mpInstance = nullptr; } } OpRes ContentManager::Load(Project* project) { Unload(); mpProject = project; std::filesystem::path root = mpProject->GetRootDirectory(); mContentFile = root / "contents/content_meta.json"; // If this is a new project being generated just create a base contents file if (!std::filesystem::exists(mContentFile)) { return Save(); } std::ifstream ifs = std::ifstream(mContentFile.string().c_str()); if (!ifs.is_open()) { return OpRes::Fail("Could not open contents file: %s", mContentFile.string().c_str()); } nlohmann::json j; ifs >> j; ifs.close(); auto p = j["Project"]; if (p.is_null()) { return OpRes::Fail("content_meta.json missing Project element"); } if (p["NextID"].is_null()) { return OpRes::Fail("content_meta.xml missing NextID node"); } mNextID = p["NextID"].get(); auto c = p["Contents"]; if (c.is_null()) { return OpRes::OK(); // Not actually an error if a project does not have any content yet //return OpRes::Fail("content_meta.xml missing Contents node"); } for (auto it = c.begin(); it != c.end(); ++it) { auto& asset = (*it); if (!IsValidAsset(asset)) { return OpRes::Fail("Invalid asset node in content_meta.json"); } // Check if asset is trashed if (asset["Trashed"].get()) { // Skip trashed assets continue; } // Load Type AssetType type = (AssetType)asset["Type"].get(); EditorAsset* pAsset = CreateAsset(type); if (!pAsset) { return OpRes::Fail("Could not create Editor Asset. Unknown type: %d", (int)type); } pAsset->mAssetDir = mpProject->GetAssetDirectory(); // If we got to this point the asset must not be trashed pAsset->mIsTrashed = false; // Load ID pAsset->mID = asset["ID"].get(); // Load Location pAsset->mLocation = std::filesystem::path(asset["Location"].get()); // Load type specific data if (Failed(pAsset->LoadFromJSON(asset).LogIfFailed(Editor::LogCat).LogIfFailed(Editor::LogCat))) { return OpRes::Fail("Could not load asset type specific data for asset with ID: %llu, File: %s", pAsset->GetID(), pAsset->GetFileLocation().filename().string().c_str()); } // Store asset auto iter = mAssets.find(pAsset->mID); if (iter != mAssets.end()) { return OpRes::Fail("Asset ID collision, First asset: ID: %llu, File: %s, Second asset: ID: %llu, File: %s", iter->second->GetID(), iter->second->GetFileLocation().filename().string().c_str(), pAsset->GetID(), pAsset->GetFileLocation().filename().string().c_str()); } mAssets[pAsset->mID] = pAsset; } return OpRes::OK(); } OpRes ContentManager::Save() { Logger::Info(Editor::LogCat, "Updating content_meta.json..."); if (!mpProject) { return OpRes::Fail("ConentManager::Save failed: no project set"); } nlohmann::ordered_json oj; auto& p = oj["Project"]; // Save header info p["Name"] = mpProject->GetName(); p["NextID"] = mNextID; // Save all assets auto& c = p["Contents"]; for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++) { EditorAsset* pAsset = iter->second; nlohmann::json asset; asset["Type"] = (i32)pAsset->GetType(); asset["ID"] = pAsset->GetID(); asset["Trashed"] = pAsset->GetIsTrashed(); // TODO: This needs to be a relative path! (Relative to the project root) asset["Location"] = pAsset->GetFileLocation().string().c_str(); if (Failed(pAsset->SaveToJSON(asset).LogIfFailed(Editor::LogCat).LogIfFailed(Editor::LogCat))) { return OpRes::Fail("Could not save asset meta data for file: %s", pAsset->GetFileLocation().string().c_str()); } //c.push_back(asset); c.emplace_back(asset); } std::ofstream ofs = std::ofstream(mContentFile.string().c_str(), std::ios_base::trunc); if (!ofs.is_open()) { return OpRes::Fail("ContentManager could not save file: %s - could not open file", mContentFile.string().c_str()); } ofs << std::setw(4) << oj; ofs.close(); return OpRes::OK(); } void ContentManager::Unload() { mpProject = nullptr; FreeAssets(); mContentFile = ""; mNextID = 0; } void ContentManager::GetAllAssetIDs(std::vector& container) const { container.clear(); for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++) { container.push_back(iter->first); } } void ContentManager::GetAllAssetsByType(std::vector& container, AssetType type) const { container.clear(); for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++) { if (iter->second->GetType() == type) { container.push_back(iter->second); } } } void ContentManager::GetAllAssetsInDirectory(std::vector& container, std::filesystem::path dir) { container.clear(); for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++) { auto temp = iter->second->GetFileLocation().remove_filename().parent_path(); if (temp == dir) { container.push_back(iter->second); } } } EditorAsset* ContentManager::GetAsset(uint64_t id) { auto iter = mAssets.find(id); if (iter == mAssets.end()) { return nullptr; } return iter->second; } OpRes ContentManager::AddGeneratedAsset(EditorAsset* asset, uint64_t& id) { if (mAssets.find(mNextID) != mAssets.end()) { return OpRes::Fail("Could not import asset, ID collision. ID: %llu", mNextID); } asset->mID = mNextID; mNextID++; mAssets[asset->mID] = asset; id = asset->mID; Save().LogIfFailed(Editor::LogCat, "Asset was created"); return OpRes::OK(); } OpRes ContentManager::ImportFile(std::filesystem::path file, std::filesystem::path to_location, AssetType type, uint64_t& id) { if (mAssets.find(mNextID) != mAssets.end()) { return OpRes::Fail("Could not import asset, ID collision. ID: %llu", mNextID); } if (std::filesystem::exists(to_location)) { std::filesystem::remove(to_location); } if (!std::filesystem::copy_file(file, to_location)) { return OpRes::Fail("Could not copy asset file from: %s to: %s", file.string().c_str(), to_location.string().c_str()); } EditorAsset* pAsset = CreateAsset(type); pAsset->mAssetDir = mpProject->GetAssetDirectory(); pAsset->mID = mNextID; mNextID++; pAsset->mLocation = mpProject->MakeRelativeToAssets(to_location); if (Failed(pAsset->LoadRawFile().LogIfFailed(Editor::LogCat))) { delete pAsset; return OpRes::Fail("Could not load asset data from raw file: %s", file.string().c_str()); } mAssets[pAsset->mID] = pAsset; id = pAsset->mID; return OpRes::OK(); } void ContentManager::RemoveAsset(uint64_t asset_id) { auto iter = mAssets.find(asset_id); if (iter == mAssets.end()) { return; } // Move the asset to a trash directory to prevent data loss iter->second->mIsTrashed = true; MoveAsset(iter->second, mpProject->GetTrashDirectory()); delete iter->second; mAssets.erase(iter); Save().LogIfFailed(Editor::LogCat, "Asset was removed"); } void ContentManager::MoveAsset(EditorAsset* asset, std::filesystem::path to) { std::filesystem::path from = mpProject->GetAssetDirectory() / asset->GetFileLocation(); // Not all assets will have files associated with them if (std::filesystem::exists(from)) { std::filesystem::rename(from, to); } std::filesystem::path r = mpProject->MakeRelativeToAssets(to); asset->mLocation = r; Save().LogIfFailed(Editor::LogCat, "Asset was moved"); } bool ContentManager::IsValidAsset(nlohmann::json& node) { if (node.is_null()) { return false; } if (node["ID"].is_null()) { return false; } if (!node["ID"].is_number_unsigned()) { return false; } if (node["Type"].is_null()) { return false; } if (!node["Type"].is_number()) { return false; } if (node["Location"].is_null()) { return false; } return true; } EditorAsset* ContentManager::CreateAsset(AssetType type) { switch (type) { case AssetType::EATYPE_TILE_SET: return new TileSet(); case AssetType::EATYPE_WORLD: return new editor::World(); default: return nullptr; } } void ContentManager::FreeAssets() { for (auto iter = mAssets.begin(); iter != mAssets.end(); iter++) { delete iter->second; } mAssets.clear(); } }}