载入中...
搜索中...
未找到
UnityPrefabExpansion.cpp
浏览该文件的文档.
1#include <regex>
2#include <set>
3#include <stdexcept>
6
7namespace eve::asset_import {
8namespace {
9struct Doc {
10 std::string type, id, body;
11 bool stripped = false;
12};
13std::string find(const std::string& text, const std::string& pattern) {
14 std::smatch m;
15 return std::regex_search(text, m, std::regex(pattern)) ? m[1].str() : std::string{};
16}
17std::vector<Doc> docs(const std::string& text) {
18 std::vector<Doc> out;
19 std::size_t start = 0;
20 const std::regex header(R"((?:^|\n)--- !u!([0-9]+) &(-?[0-9]+)([^\n]*)\n)");
21 for (std::sregex_iterator it(text.begin(), text.end(), header), end; it != end; ++it) {
22 if (!out.empty()) out.back().body = text.substr(start, std::size_t(it->position()) - start);
23 out.push_back({(*it)[1], (*it)[2], {}, (*it)[3].str().find("stripped") != std::string::npos});
24 start = std::size_t(it->position() + it->length());
25 }
26 if (!out.empty()) out.back().body = text.substr(start);
27 return out;
28}
29void replaceCapture(std::string& body, const std::string& pattern, const std::string& value) {
30 std::smatch m;
31 if (!std::regex_search(body, m, std::regex(pattern))) throw std::runtime_error("Prefab override field missing");
32 body.replace(std::size_t(m.position(1)), std::size_t(m.length(1)), value);
33}
34struct Expander {
35 const UnityProjectImportRequest& request;
36 std::map<std::string, std::string> paths;
37 std::set<std::string> visiting;
38 std::vector<ImportFinding> findings;
39 std::vector<Doc> expand(const std::string& guid, unsigned depth) {
40 if (depth > 32 || !visiting.insert(guid).second)
41 throw std::runtime_error("Prefab dependency cycle or depth budget exceeded");
42 if (!paths.contains(guid)) throw std::runtime_error("Prefab source GUID is missing");
43 const auto& path = paths.at(guid);
44 const auto& bytes = request.files.at(path);
45 std::string text(bytes.begin(), bytes.end());
46 std::erase(text, '\r');
47 auto original = docs(text);
48 std::vector<Doc> out;
49 std::set<std::string> used;
50 for (const auto& d : original)
51 if (!used.insert(d.id).second) throw std::runtime_error("duplicate Prefab fileID");
52 for (const auto& d : original)
53 if (d.type != "1001" && !d.stripped) out.push_back(d);
54 for (const auto& instance : original) {
55 if (instance.type != "1001") continue;
56 const auto version = find(instance.body, R"(serializedVersion: *([0-9]+))");
57 if (!version.empty() && version != "2") throw std::runtime_error("unsupported PrefabInstance version");
58 if (instance.body.find("m_RemovedComponents:") == std::string::npos)
59 throw std::runtime_error("Prefab removed-component list missing");
60 const auto source = find(instance.body, R"(m_SourcePrefab: *\{fileID: *[0-9]+, guid: *([0-9a-fA-F]{32}))");
61 const auto parent = find(instance.body, R"(m_TransformParent: *\{fileID: *(-?[0-9]+)\})");
62 if (source.empty() || parent.empty()) throw std::runtime_error("Prefab instance source or parent missing");
63 auto children = expand(unity_detail::foldAscii(source), depth + 1);
64 const auto removed = find(instance.body, R"(m_RemovedComponents:([\s\S]*?)\n m_SourcePrefab:)");
65 const std::regex removal(R"(fileID: (-?[0-9]+), guid: ([0-9a-fA-F]{32}))");
66 for (std::sregex_iterator it(removed.begin(), removed.end(), removal), end; it != end; ++it) {
68 throw std::runtime_error("removed-component GUID mismatch");
69 const auto id = (*it)[1].str();
70 for (const auto& child : children)
71 if (child.id == id && (child.type == "1" || child.type == "4"))
72 throw std::runtime_error("cannot remove hierarchy as component");
73 std::erase_if(children, [&](const auto& child) { return child.id == id; });
74 }
75 std::map<std::string, std::string> ids;
76 for (const auto& stripped : original)
77 if (stripped.stripped &&
78 find(stripped.body, R"(m_PrefabInstance: *\{fileID: *(-?[0-9]+)\})") == instance.id) {
79 const auto target = find(stripped.body, R"(m_CorrespondingSourceObject: *\{fileID: *(-?[0-9]+),)");
80 if (target.empty() || !ids.emplace(target, stripped.id).second)
81 throw std::runtime_error("invalid stripped Prefab alias");
82 }
83 std::map<std::string, std::size_t> byId;
84 for (std::size_t i = 0; i < children.size(); ++i) byId.emplace(children[i].id, i);
85 const std::regex modification(
86 R"( - target: \{fileID: (-?[0-9]+), guid: ([0-9a-fA-F]{32}),\s*type: [0-9]+\}\n propertyPath: ([^\n]+)\n value:([^\n]*)\n objectReference: (\{[^\n]+\}))");
87 for (std::sregex_iterator it(instance.body.begin(), instance.body.end(), modification), end; it != end;
88 ++it) {
90 throw std::runtime_error("Prefab override source GUID mismatch");
91 const auto id = (*it)[1].str(), property = (*it)[3].str();
92 auto value = (*it)[4].str();
93 if (!value.empty() && value.front() == ' ') value.erase(0, 1);
94 if (!byId.contains(id)) {
95 findings.push_back({path, "Prefab.unusedOverride", ImportDisposition::PreservedSource,
96 "override target no longer exists in source prefab: " + id + " " + property});
97 continue;
98 }
99 auto& target = children[byId.at(id)];
100 if (target.type != "1" && target.type != "4" && target.type != "23" && target.type != "33") {
101 findings.push_back({path, "Prefab.nonVisualOverride", ImportDisposition::Unsupported,
102 "override on a non-converted component: " + property});
103 continue;
104 }
105 if (property == "m_RootOrder" || property == "m_StaticEditorFlags" ||
106 property.starts_with("m_LocalEulerAnglesHint."))
107 continue;
108 if (property.starts_with("m_LocalPosition.") || property.starts_with("m_LocalRotation.") ||
109 property.starts_with("m_LocalScale.")) {
110 const auto dot = property.find('.');
111 const auto field = property.substr(0, dot);
112 const auto axis = property.substr(dot + 1);
113 if (axis.size() != 1 || std::string("xyzw").find(axis) == std::string::npos)
114 throw std::runtime_error("invalid transform override axis");
115 replaceCapture(target.body, field + ": *\\{[^}]*?" + axis + ": *([^,}]+)", value);
116 } else if (property == "m_Name" || property == "m_IsActive" || property == "m_Enabled") {
117 replaceCapture(target.body, "(?:^|\\n) " + property + ": *([^\\n]*)", value);
118 } else if (property == "m_Mesh") {
119 replaceCapture(target.body, R"(m_Mesh: *(\{[^\n]+\}))", (*it)[5]);
120 } else
121 throw std::runtime_error("unsupported visual Prefab override: " + property);
122 }
123 for (const auto& child : children)
124 if (!ids.contains(child.id)) {
125 auto identity =
126 request.package.packageId.child("prefab-expand:" + guid + ":" + instance.id + ":" + child.id);
127 std::uint64_t value = 0;
128 for (unsigned i = 0; i < 8; ++i) value = (value << 8) | identity.bytes()[i + 8];
129 value &= 0x7fffffffffffffffull;
130 const auto id = std::to_string(value);
131 if (value == 0 || !used.insert(id).second)
132 throw std::runtime_error("expanded Prefab identity collision");
133 ids.emplace(child.id, id);
134 }
135 for (auto& child : children) {
136 if (child.type == "4" && find(child.body, R"(m_Father: *\{fileID: *(-?[0-9]+)\})") == "0")
137 replaceCapture(child.body, R"(m_Father: *(\{fileID: *0\}))", "{fileID: __parent__}");
138 const std::regex local(R"(\{fileID: (-?[0-9]+)\})");
139 std::string rewritten;
140 std::size_t start = 0;
141 for (std::sregex_iterator it(child.body.begin(), child.body.end(), local), end; it != end; ++it) {
142 rewritten += child.body.substr(start, std::size_t(it->position()) - start);
143 const auto id = (*it)[1].str();
144 rewritten += "{fileID: " + (ids.contains(id) ? ids.at(id) : id) + "}";
145 start = std::size_t(it->position() + it->length());
146 }
147 rewritten += child.body.substr(start);
148 const auto marker = rewritten.find("__parent__");
149 if (marker != std::string::npos) rewritten.replace(marker, 10, parent);
150 child.body = std::move(rewritten);
151 child.id = ids.at(child.id);
152 child.stripped = false;
153 out.push_back(std::move(child));
154 if (out.size() > request.limits.maximumAssets)
155 throw std::runtime_error("expanded Prefab object budget exceeded");
156 }
157 }
158 visiting.erase(guid);
159 return out;
160 }
161};
162} // namespace
164 const UnitySourceAsset& source) {
165 try {
166 Expander expander{request, {}, {}, {}};
167 for (const auto& asset : index.assets)
168 if (asset.kind == UnitySourceKind::Prefab) expander.paths.emplace(asset.guid, asset.path);
169 auto expanded = expander.expand(source.guid, 0);
170 std::string text = "%YAML 1.1\n";
171 for (const auto& doc : expanded) {
172 text += "--- !u!" + doc.type + " &" + doc.id + "\n" + doc.body;
173 if (text.back() != '\n') text += '\n';
175 throw std::runtime_error("expanded Prefab byte budget exceeded");
176 }
177 return Result<UnityExpandedPrefab>::success({{text.begin(), text.end()}, std::move(expander.findings)});
178 } catch (const std::exception& e) {
180 Diagnostic::error(DiagnosticCode::ParseError, e.what(), source.path, {}, "asset.import.unity"));
181 }
182}
183} // namespace eve::asset_import
LogicalId target
double value
Duration start
std::string pattern
const GltfImportRequest & request
glm::uvec4 ids
std::string text
std::string local
std::int32_t parent
std::uint64_t bytes
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float d
int removed
int children
Definition TreeMesh.cpp:295
std::string body
Direct Unity text-serialization adapter for TerrainData and Prefab assets.
std::set< std::string > visiting
std::map< std::string, std::string > paths
bool stripped
std::vector< ImportFinding > findings
uint32_t index
const UnitySourceAsset & source
std::uint32_t depth
float m[16]
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
std::string foldAscii(std::string value)
Fold ascii.
Result< UnityExpandedPrefab > expandUnityPrefab(const UnityProjectImportRequest &request, const UnitySourceIndex &index, const UnitySourceAsset &source)
Resolve bounded nested prefab references into an unpublished owning text candidate.
std::string header(int localX, int localY)
Header.
Definition KernelGen.cpp:13
In-memory Unity project slice; keys are normalized project-relative paths.
Immutable-by-convention owning source index entry; no borrowed source addresses.
Definition UnitySource.h:38
Owning deterministic index sorted by path, plus missing-reference diagnostics.
Definition UnitySource.h:47
NodeType type
Definition Widget.h:16
std::string id
Definition Widget.h:17