载入中...
搜索中...
未找到
PointDelta.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <bit>
5#include <string>
6#include <type_traits>
7#include <unordered_map>
8#include <unordered_set>
9
10namespace eve::procgen {
11namespace {
12
13template <class T>
14void hashValue(std::uint64_t& hash, const T& value) {
15 if constexpr (std::is_enum_v<T>) {
16 hashValue(hash, static_cast<std::underlying_type_t<T>>(value));
17 } else if constexpr (std::is_same_v<T, bool>) {
18 hashValue(hash, std::uint8_t(value ? 1 : 0));
19 } else if constexpr (std::is_floating_point_v<T>) {
20 using Bits = std::conditional_t<sizeof(T) == 4, std::uint32_t, std::uint64_t>;
21 hashValue(hash, std::bit_cast<Bits>(value));
22 } else {
23 using Bits = std::make_unsigned_t<T>;
24 const Bits bits = Bits(value);
25 for (std::size_t index = 0; index < sizeof(Bits); ++index) {
26 hash ^= static_cast<unsigned char>(bits >> (index * 8u));
27 hash *= 1099511628211ull;
28 }
29 }
30}
31
32void hashText(std::uint64_t& hash, std::string_view value) {
33 for (const unsigned char byte : value) {
34 hash ^= byte;
35 hash *= 1099511628211ull;
36 }
37 const unsigned char terminator = 0;
38 hashValue(hash, terminator);
39}
40
41std::vector<std::string> sortedColumns(const AttributeTable& attributes) {
42 std::vector<std::string> names;
43 names.reserve(attributes.columnCount());
44 for (std::size_t column = 0; column < attributes.columnCount(); ++column)
45 names.emplace_back(attributes.columnName(column));
46 std::sort(names.begin(), names.end());
47 return names;
48}
49
50bool schemasEqual(const AttributeTable& left, const AttributeTable& right) {
51 const auto leftNames = sortedColumns(left);
52 const auto rightNames = sortedColumns(right);
53 if (leftNames != rightNames) return false;
54 return std::all_of(leftNames.begin(), leftNames.end(), [&](const std::string& name) {
55 return left.typeOf(name) == right.typeOf(name);
56 });
57}
58
59bool attributeRowsEqual(const AttributeTable& left, std::size_t leftRow, const AttributeTable& right,
60 std::size_t rightRow) {
61 if (!schemasEqual(left, right)) return false;
62 for (const auto& name : sortedColumns(left)) {
63 if (left.has(leftRow, name) != right.has(rightRow, name)) return false;
64 if (!left.has(leftRow, name)) continue;
65 switch (*left.typeOf(name)) {
67 if (left.getFloat(leftRow, name) != right.getFloat(rightRow, name)) return false;
68 break;
70 if (left.getInt(leftRow, name) != right.getInt(rightRow, name)) return false;
71 break;
73 if (left.getBool(leftRow, name) != right.getBool(rightRow, name)) return false;
74 break;
76 const auto a = left.getVector(leftRow, name);
77 const auto b = right.getVector(rightRow, name);
78 if (!a || !b || a->x != b->x || a->y != b->y || a->z != b->z) return false;
79 break;
80 }
82 if (left.getString(leftRow, name) != right.getString(rightRow, name)) return false;
83 break;
84 }
85 }
86 return true;
87}
88
89bool pointsEqual(const ProcgenPoint& a, const ProcgenPoint& b) {
90 return a.id == b.id && a.x == b.x && a.y == b.y && a.z == b.z && a.normalX == b.normalX &&
91 a.normalY == b.normalY && a.normalZ == b.normalZ && a.pitch == b.pitch && a.yaw == b.yaw &&
92 a.roll == b.roll && a.scaleX == b.scaleX && a.scaleY == b.scaleY && a.scaleZ == b.scaleZ &&
93 a.density == b.density && a.seed == b.seed && a.boundsMinX == b.boundsMinX &&
94 a.boundsMinY == b.boundsMinY && a.boundsMinZ == b.boundsMinZ && a.boundsMaxX == b.boundsMaxX &&
95 a.boundsMaxY == b.boundsMaxY && a.boundsMaxZ == b.boundsMaxZ && a.colorR == b.colorR &&
96 a.colorG == b.colorG && a.colorB == b.colorB && a.colorA == b.colorA && a.steepness == b.steepness;
97}
98
99Result<std::unordered_map<std::uint64_t, std::size_t>> indexPoints(const PointSet& points) {
100 std::unordered_map<std::uint64_t, std::size_t> index;
101 index.reserve(points.points().size());
102 for (std::size_t row = 0; row < points.points().size(); ++row) {
103 const auto id = points.points()[row].id;
104 if (id == 0)
105 return Result<std::unordered_map<std::uint64_t, std::size_t>>::failure(
106 Diagnostic::error(DiagnosticCode::InvalidArgument, "point delta requires non-zero ids", "points.id"));
107 if (!index.emplace(id, row).second)
108 return Result<std::unordered_map<std::uint64_t, std::size_t>>::failure(
109 Diagnostic::error(DiagnosticCode::Conflict, "point delta requires unique ids", "points.id"));
110 }
111 return Result<std::unordered_map<std::uint64_t, std::size_t>>::success(std::move(index));
112}
113
114void hashAttributeRow(std::uint64_t& hash, const AttributeTable& attributes, std::size_t row) {
115 for (const auto& name : sortedColumns(attributes)) {
116 hashText(hash, name);
117 const auto type = *attributes.typeOf(name);
118 hashValue(hash, type);
119 const bool present = attributes.has(row, name);
120 hashValue(hash, present);
121 if (!present) continue;
122 switch (type) {
123 case ProcgenAttributeType::Float: hashValue(hash, *attributes.getFloat(row, name)); break;
124 case ProcgenAttributeType::Int: hashValue(hash, *attributes.getInt(row, name)); break;
125 case ProcgenAttributeType::Bool: hashValue(hash, *attributes.getBool(row, name)); break;
127 const auto value = *attributes.getVector(row, name);
128 hashValue(hash, value.x);
129 hashValue(hash, value.y);
130 hashValue(hash, value.z);
131 break;
132 }
133 case ProcgenAttributeType::String: hashText(hash, *attributes.getString(row, name)); break;
134 }
135 }
136}
137
138} // namespace
139
141 auto indexed = indexPoints(points);
142 if (!indexed.ok()) return Result<std::uint64_t>::failure(indexed.status());
143 std::uint64_t hash = 14695981039346656037ull;
144 const auto count = points.points().size();
145 hashValue(hash, count);
146 for (std::size_t row = 0; row < count; ++row) {
147 const auto& point = points.points()[row];
148 hashValue(hash, point.id);
149 hashValue(hash, point.x);
150 hashValue(hash, point.y);
151 hashValue(hash, point.z);
152 hashValue(hash, point.normalX);
153 hashValue(hash, point.normalY);
154 hashValue(hash, point.normalZ);
155 hashValue(hash, point.pitch);
156 hashValue(hash, point.yaw);
157 hashValue(hash, point.roll);
158 hashValue(hash, point.scaleX);
159 hashValue(hash, point.scaleY);
160 hashValue(hash, point.scaleZ);
161 hashValue(hash, point.density);
162 hashValue(hash, point.seed);
163 hashValue(hash, point.boundsMinX);
164 hashValue(hash, point.boundsMinY);
165 hashValue(hash, point.boundsMinZ);
166 hashValue(hash, point.boundsMaxX);
167 hashValue(hash, point.boundsMaxY);
168 hashValue(hash, point.boundsMaxZ);
169 hashValue(hash, point.colorR);
170 hashValue(hash, point.colorG);
171 hashValue(hash, point.colorB);
172 hashValue(hash, point.colorA);
173 hashValue(hash, point.steepness);
174 hashAttributeRow(hash, points.attributes(), row);
175 }
176 return Result<std::uint64_t>::success(hash == 0 ? 1 : hash);
177}
178
179Result<PointDelta> diffPointSets(const PointSet& before, const PointSet& after) {
180 auto beforeIndex = indexPoints(before);
181 if (!beforeIndex.ok()) return Result<PointDelta>::failure(beforeIndex.status());
182 auto afterIndex = indexPoints(after);
183 if (!afterIndex.ok()) return Result<PointDelta>::failure(afterIndex.status());
184 auto baseFingerprint = fingerprintPointSet(before);
185 if (!baseFingerprint.ok()) return Result<PointDelta>::failure(baseFingerprint.status());
186 auto targetFingerprint = fingerprintPointSet(after);
187 if (!targetFingerprint.ok()) return Result<PointDelta>::failure(targetFingerprint.status());
188
189 PointDelta delta;
190 delta.baseFingerprint = baseFingerprint.value();
191 delta.targetFingerprint = targetFingerprint.value();
192 delta.targetOrder.reserve(after.points().size());
193 for (const auto& point : before.points())
194 if (!afterIndex.value().contains(point.id)) delta.removed.push_back(point.id);
195 const bool sameSchema = schemasEqual(before.attributes(), after.attributes());
196 for (std::size_t row = 0; row < after.points().size(); ++row) {
197 const auto id = after.points()[row].id;
198 delta.targetOrder.push_back(id);
199 const auto found = beforeIndex.value().find(id);
200 if (found == beforeIndex.value().end()) {
201 auto appended = delta.added.appendPointFrom(after, row);
202 if (!appended.ok()) return Result<PointDelta>::failure(appended.status());
203 } else if (!sameSchema || !pointsEqual(before.points()[found->second], after.points()[row]) ||
204 !attributeRowsEqual(before.attributes(), found->second, after.attributes(), row)) {
205 auto appended = delta.updated.appendPointFrom(after, row);
206 if (!appended.ok()) return Result<PointDelta>::failure(appended.status());
207 }
208 }
209 return Result<PointDelta>::success(std::move(delta));
210}
211
213 auto baseFingerprint = fingerprintPointSet(base);
214 if (!baseFingerprint.ok()) return Result<PointSet>::failure(baseFingerprint.status());
215 if (baseFingerprint.value() != delta.baseFingerprint)
217 Diagnostic::error(DiagnosticCode::Conflict, "point delta base fingerprint is stale", "base"));
218 auto baseIndex = indexPoints(base);
219 auto addedIndex = indexPoints(delta.added);
220 auto updatedIndex = indexPoints(delta.updated);
221 if (!baseIndex.ok()) return Result<PointSet>::failure(baseIndex.status());
222 if (!addedIndex.ok()) return Result<PointSet>::failure(addedIndex.status());
223 if (!updatedIndex.ok()) return Result<PointSet>::failure(updatedIndex.status());
224 for (const auto& [id, row] : addedIndex.value()) {
225 (void)row;
226 if (baseIndex.value().contains(id) || updatedIndex.value().contains(id))
228 Diagnostic::error(DiagnosticCode::Conflict, "point delta added identity is inconsistent", "added"));
229 }
230 for (const auto& [id, row] : updatedIndex.value()) {
231 (void)row;
232 if (!baseIndex.value().contains(id))
234 DiagnosticCode::Conflict, "point delta updated identity is not in the base", "updated"));
235 }
236
237 std::unordered_set<std::uint64_t> removed(delta.removed.begin(), delta.removed.end());
238 if (removed.size() != delta.removed.size())
240 Diagnostic::error(DiagnosticCode::Conflict, "point delta contains duplicate removals", "removed"));
241 for (const auto id : removed)
242 if (!baseIndex.value().contains(id) || updatedIndex.value().contains(id))
244 Diagnostic::error(DiagnosticCode::Conflict, "point delta removal is inconsistent", "removed"));
245
246 PointSet staged;
247 staged.reserve(delta.targetOrder.size());
248 std::unordered_set<std::uint64_t> emitted;
249 emitted.reserve(delta.targetOrder.size());
250 for (const auto id : delta.targetOrder) {
251 if (id == 0 || !emitted.insert(id).second || removed.contains(id))
253 Diagnostic::error(DiagnosticCode::Conflict, "point delta target order is inconsistent", "targetOrder"));
254 const PointSet* source = nullptr;
255 std::size_t row = 0;
256 if (const auto found = addedIndex.value().find(id); found != addedIndex.value().end()) {
257 source = &delta.added;
258 row = found->second;
259 } else if (const auto found = updatedIndex.value().find(id); found != updatedIndex.value().end()) {
260 source = &delta.updated;
261 row = found->second;
262 } else if (const auto found = baseIndex.value().find(id); found != baseIndex.value().end()) {
263 source = &base;
264 row = found->second;
265 } else {
267 Diagnostic::error(DiagnosticCode::Conflict, "point delta references an unknown id", "targetOrder"));
268 }
269 auto appended = staged.appendPointFrom(*source, row);
270 if (!appended.ok()) return Result<PointSet>::failure(appended.status());
271 }
272 if (emitted.size() != base.points().size() - removed.size() + delta.added.points().size())
274 Diagnostic::error(DiagnosticCode::Conflict, "point delta omits or duplicates points", "targetOrder"));
275 auto fingerprint = fingerprintPointSet(staged);
276 if (!fingerprint.ok()) return Result<PointSet>::failure(fingerprint.status());
277 if (fingerprint.value() != delta.targetFingerprint)
279 Diagnostic::error(DiagnosticCode::Conflict, "point delta target fingerprint does not match", "delta"));
280 return Result<PointSet>::success(std::move(staged));
281}
282
283} // namespace eve::procgen
double value
int column
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
HexVec3 left
HexVec3 right
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::shared_ptr< const std::vector< glm::vec2 > > points
bool found
int removed
std::uint32_t count
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
glm::vec3 point
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
Script-friendly collection of attributed 3D samples.
Definition PointSet.h:59
const std::vector< ProcgenPoint > & points() const
Borrow immutable point rows; structural ownership remains with this set.
Definition PointSet.h:212
Result< int > appendPointFrom(const PointSet &source, std::size_t sourceIndex)
Append a point and its attributes from another set.
Definition PointSet.cpp:69
const AttributeTable & attributes() const noexcept
Borrow the authoritative schema-bearing attribute table.
Definition PointSet.h:216
void reserve(std::size_t count)
Reserve point storage without changing point or attribute row counts.
Definition PointSet.cpp:60
Result< PointSet > applyPointDelta(const PointSet &base, const PointDelta &delta)
Apply a delta atomically, rejecting stale or internally inconsistent input.
Result< std::uint64_t > fingerprintPointSet(const PointSet &points)
Compute an exact deterministic fingerprint, rejecting missing or duplicate ids.
Result< PointDelta > diffPointSets(const PointSet &before, const PointSet &after)
Compute an identity-based delta without mutating either snapshot.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679
Transactional identity-based change set between two point snapshots.
Definition PointDelta.h:16
std::uint64_t targetFingerprint
Fingerprint that the fully applied target snapshot must produce.
Definition PointDelta.h:28
PointSet added
Target-only points, including their complete attribute rows.
Definition PointDelta.h:18
PointSet updated
Base identities whose point or attribute content changed.
Definition PointDelta.h:20
std::vector< std::uint64_t > removed
Base identities absent from the target snapshot.
Definition PointDelta.h:22
std::vector< std::uint64_t > targetOrder
Exact identity order of the target snapshot.
Definition PointDelta.h:24
std::uint64_t baseFingerprint
Fingerprint that the input snapshot must match before application.
Definition PointDelta.h:26