#include "BuildSystem.h" #include "AssetDatabase.h" #include "Meta.h" #include "Project.h" #include "EditorResource.h" #include "Core/AssetBundle.h" #include "Core/FileSystem.h" #include "Game/World.h" #include "Game/GameObject.h" #include "Game/GameManager.h" #include "Game/Asset/WorldAsset.h" #include "Game/Asset/ShaderAsset.h" #include "Game/Asset/MaterialAsset.h" #include "Game/Asset/MeshAsset.h" #include "Game/Asset/TextureAsset.h" #include namespace sh::editor { BuildSystem::BuildSystem() : uuidRegex("^[0-9a-f]{32}$", std::regex::optimize) { } SH_EDITOR_API void BuildSystem::Build(Project& project, const std::filesystem::path& outputPath) { worldUUIDs.clear(); uuids.clear(); currentProject = &project; core::AssetBundle bundle; std::vector worldPtrs; if (!project.GetProjectSetting().startingWorldUUID.IsEmpty()) { auto objPtr = core::SObjectManager::GetInstance()->GetSObject(project.GetProjectSetting().startingWorldUUID); worldPtrs.push_back(static_cast(objPtr)); } for (int i = 0; i < worldPtrs.size(); ++i) { auto worldPtr = worldPtrs[i]; core::Json worldJson{}; if (worldPtr->IsLoaded()) worldJson = worldPtr->Serialize(); else { core::Json* worldPointPtr = worldPtr->GetWorldPoint(); if (worldPointPtr == nullptr) { SH_WARN_FORMAT("World({}) is empty!", worldPtr->GetUUID().ToString()); continue; } worldJson = *worldPointPtr; } std::unordered_set uuids; ExtractUUIDs(uuids, worldJson); for (const auto& uuidStr : uuids) { const core::UUID uuid{ uuidStr }; this->uuids.insert(uuidStr); worldUUIDs[worldPtr->GetUUID().ToString()].push_back(uuidStr); auto obj = core::SObjectManager::GetInstance()->GetSObject(uuid); if (obj != nullptr) { if (obj->GetType().IsChildOf(game::World::GetStaticType())) { auto it = std::find(worldPtrs.begin(), worldPtrs.end(), static_cast(obj)); if (it == worldPtrs.end()) worldPtrs.push_back(static_cast(obj)); } } } PackingAssets(bundle, *worldPtr); } bundle.SaveBundle(outputPath / "assets.bundle"); ExportGameManager(outputPath / "gameManager.bin"); CopyRuntimeBinaries(outputPath); } void BuildSystem::CopyRuntimeBinaries(const std::filesystem::path& outputPath) { const std::filesystem::path engineDir = core::FileSystem::GetExecutableDirectory(); #if _WIN32 constexpr const char* exeExt = ".exe"; constexpr const char* libExt = ".dll"; constexpr const char* libPrefix = ""; #else constexpr const char* exeExt = ""; constexpr const char* libExt = ".so"; constexpr const char* libPrefix = "lib"; #endif const std::vector executables = { "ShellGame" }; const std::vector libraries = { "ShellEngineCore", "ShellEngineWindow", "ShellEngineRender", "ShellEnginePhysics", "ShellEngineGame", "ShellEngineNetwork", "ShellEngineSound" }; #if _WIN32 const std::vector extraFiles = { "OpenAL32.dll" }; #else const std::vector extraFiles = { "libopenal.so" }; #endif const std::vector directories = { "fonts" }; std::error_code ec; if (!std::filesystem::exists(outputPath)) std::filesystem::create_directories(outputPath, ec); auto copyIfMissingFn = [&](const std::filesystem::path& src, const std::filesystem::path& dst) { if (!std::filesystem::exists(src)) { SH_WARN_FORMAT("Runtime binary not found in engine path: {}", src.u8string()); return; } std::error_code copyEc; std::filesystem::copy_file(src, dst, std::filesystem::copy_options::overwrite_existing, copyEc); if (copyEc) SH_ERROR_FORMAT("Failed to copy {} -> {}: {}", src.u8string(), dst.u8string(), copyEc.message()); }; for (const std::string& name : executables) { const std::string fileName = name + exeExt; copyIfMissingFn(engineDir / fileName, outputPath / fileName); } for (const std::string& name : libraries) { const std::string fileName = libPrefix + name + libExt; copyIfMissingFn(engineDir / fileName, outputPath / fileName); } for (const std::string& fileName : extraFiles) { copyIfMissingFn(engineDir / fileName, outputPath / fileName); } for (const std::string& dirName : directories) { const std::filesystem::path src = engineDir / dirName; const std::filesystem::path dst = outputPath / dirName; if (!std::filesystem::exists(src)) { SH_WARN_FORMAT("Runtime directory not found in engine path: {}", src.u8string()); continue; } std::error_code copyEc; std::filesystem::copy(src, dst, std::filesystem::copy_options::recursive | std::filesystem::copy_options::overwrite_existing, copyEc); if (copyEc) SH_ERROR_FORMAT("Failed to copy directory {} -> {}: {}", src.u8string(), dst.u8string(), copyEc.message()); } } void BuildSystem::ExtractUUIDs(std::unordered_set& set, const core::Json& worldJson) { if (worldJson.is_object()) { for (auto const& [key, val] : worldJson.items()) { ExtractUUIDs(set, val); } } else if (worldJson.is_array()) { for (const auto& item : worldJson) { ExtractUUIDs(set, item); } } else if (worldJson.is_string()) { const std::string& value = worldJson.get(); if (std::regex_match(value, uuidRegex)) { if (set.find(value) == set.end()) { set.insert(value); core::SObject* obj = core::SObjectManager::GetInstance()->GetSObject(core::UUID{ value }); if (core::IsValid(obj)) { ExtractUUIDs(set,obj->Serialize()); } } } } } void BuildSystem::PackingAssets(core::AssetBundle& bundle, game::World& world) { auto editorResource = EditorResource::GetInstance(); game::ShaderAsset errorShaderAsset{ *editorResource->GetShader("ErrorShader") }; bundle.AddAsset(errorShaderAsset, true); game::ShaderAsset lineShaderAsset{ *editorResource->GetShader("Line") }; bundle.AddAsset(lineShaderAsset, true); game::ShaderAsset uiTextShaderAsset{ *editorResource->GetShader("UITextShader") }; bundle.AddAsset(uiTextShaderAsset, true); game::ShaderAsset ssaoShaderAsset{ *editorResource->GetShader("SSAOShader") }; bundle.AddAsset(ssaoShaderAsset, true); game::MaterialAsset errorMatAsset{ *editorResource->GetMaterial("ErrorMaterial") }; bundle.AddAsset(errorMatAsset, true); game::MaterialAsset lineMatAsset{ *editorResource->GetMaterial("LineMaterial") }; bundle.AddAsset(lineMatAsset, true); game::MaterialAsset uiTextMatAsset{ *editorResource->GetMaterial("UITextMaterial") }; bundle.AddAsset(uiTextMatAsset, true); game::MeshAsset cubeMesh{ *editorResource->GetModel("CubeModel")->GetMeshes()[0] }; bundle.AddAsset(cubeMesh, true); game::MeshAsset sphereMesh{ *editorResource->GetModel("SphereModel")->GetMeshes()[0] }; bundle.AddAsset(sphereMesh, true); game::MeshAsset planeMesh{ *editorResource->GetModel("PlaneModel")->GetMeshes()[0] }; bundle.AddAsset(planeMesh, true); game::TextureAsset blackTex{ *editorResource->GetTexture("BlackTexture") }; bundle.AddAsset(blackTex, true); for (const auto& uuid : uuids) { auto obj = core::SObjectManager::GetInstance()->GetSObject(core::UUID{ uuid }); if (obj != nullptr) { if (obj->GetType().IsChildOf(game::World::GetStaticType())) continue; } auto asset = AssetDatabase::GetInstance()->GetAsset(core::UUID{ uuid }); if (asset != nullptr) bundle.AddAsset(*asset, true); } game::WorldAsset worldAsset{ world }; worldAsset.ConvertToGameWorldType(); bundle.AddAsset(worldAsset, true); } void BuildSystem::ExportGameManager(const std::filesystem::path& outputPath) { game::GameManager& manager = *game::GameManager::GetInstance(); ProjectSetting& projectSetting = currentProject->GetProjectSetting(); core::Json mainJson; if (!projectSetting.startingWorldUUID.IsEmpty()) mainJson["starting"] = projectSetting.startingWorldUUID.ToString(); for (const auto& [worldUUIDStr, uuids] : worldUUIDs) { core::Json worldUUIDs{}; for (const auto& uuid : uuids) { worldUUIDs[worldUUIDStr].push_back(uuid); } mainJson["uuids"].push_back(std::move(worldUUIDs)); } const std::vector data{ core::Json::to_bson(mainJson) }; std::ofstream of{ outputPath, std::ios_base::binary }; if (!of.is_open()) { SH_ERROR_FORMAT("Failed to export game setting!: {}", outputPath.u8string()); return; } of.write(reinterpret_cast(data.data()), data.size()); of.close(); } }//namespace