Keep CMake module names and trim switches, but stop flattening editing/editor and runtime bridges as top-level src/modules directories. Parent scans skip nested facets so runtime-3d can drop authoring by directory leaf. Co-authored-by: Cursor <cursoragent@cursor.com>
179 lines
8.4 KiB
C++
179 lines
8.4 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "editor/EditorAssetDatabase.h"
|
|
#include "editor/EditorAuthority.h"
|
|
#include "editor/EditorDocumentService.h"
|
|
#include "scene/editor/EditorSceneTarget.h"
|
|
#include "editor/EditorTransactionService.h"
|
|
|
|
#include <string>
|
|
|
|
using namespace eve::editor;
|
|
|
|
TEST_CASE("editor.v2.asset_database_import_query_dependencies_and_page_generation") {
|
|
MemoryAssetDatabase database;
|
|
ImportCoordinator imports(&database);
|
|
ImportProduct texture;
|
|
texture.record.guid = AssetGuid("asset-texture");
|
|
texture.record.logicalUri = "content://Textures/Coaster.png";
|
|
texture.record.typeId = "texture";
|
|
texture.record.sourceUri = "source://Coaster.png";
|
|
texture.record.importerId = "image.importer";
|
|
texture.record.tags = {"park", "metal"};
|
|
CHECK(imports.publish(std::move(texture)).ok());
|
|
|
|
ImportProduct material;
|
|
material.record.guid = AssetGuid("asset-material");
|
|
material.record.logicalUri = "content://Materials/Coaster.evmaterial";
|
|
material.record.typeId = "material";
|
|
material.record.sourceUri = "source://Coaster.material";
|
|
material.record.importerId = "material.importer";
|
|
material.dependencies.push_back(
|
|
{material.record.guid, AssetGuid("asset-texture"), DependencyKind::Hard, PropertyPath("baseColor")});
|
|
CHECK(imports.publish(std::move(material)).ok());
|
|
|
|
AssetQuery query;
|
|
query.text = "coaster";
|
|
auto firstPage = database.query(query, 0, 1);
|
|
CHECK(firstPage.ok());
|
|
CHECK_EQ(firstPage.value().values.size(), static_cast<std::size_t>(1));
|
|
CHECK(firstPage.value().hasMore);
|
|
const std::uint64_t generation = firstPage.value().generation;
|
|
auto secondPage = database.query(query, firstPage.value().nextOffset, 1, generation);
|
|
CHECK(secondPage.ok());
|
|
CHECK_EQ(secondPage.value().values.size(), static_cast<std::size_t>(1));
|
|
CHECK_EQ(database.dependencies(AssetGuid("asset-material")).size(), static_cast<std::size_t>(1));
|
|
CHECK_EQ(database.dependencies(AssetGuid("asset-texture"), true).size(), static_cast<std::size_t>(1));
|
|
|
|
AssetRecord extra;
|
|
extra.guid = AssetGuid("asset-extra");
|
|
extra.logicalUri = "content://Other/Extra.asset";
|
|
extra.typeId = "generic";
|
|
CHECK(database.publish(std::move(extra)).ok());
|
|
auto expired = database.query(query, firstPage.value().nextOffset, 1, generation);
|
|
CHECK_EQ(static_cast<int>(expired.code()), static_cast<int>(EditorStatus::Conflict));
|
|
}
|
|
|
|
TEST_CASE("editor.v2.document_save_ticket_preserves_newer_dirty_edits") {
|
|
MemoryAtomicDocumentStore store;
|
|
DocumentService documents(&store);
|
|
DocumentKey key{DocumentKind::Scene, AssetGuid("scene-asset")};
|
|
auto opened = documents.open(key, "Park", "content://World/Park.evscene");
|
|
CHECK(opened.ok());
|
|
const DocumentId id = opened.value().id;
|
|
|
|
EditorValue first(EditorValue::Object{{"version", EditorValue(1)}});
|
|
auto editOne = documents.edit(id, first);
|
|
CHECK(editOne.ok());
|
|
auto ticket = documents.requestSave(id);
|
|
CHECK(ticket.ok());
|
|
|
|
EditorValue second(EditorValue::Object{{"version", EditorValue(2)}});
|
|
auto editTwo = documents.edit(id, second, editOne.value().revision.edit);
|
|
CHECK(editTwo.ok());
|
|
auto savedOldRevision = documents.executeSave(ticket.value());
|
|
CHECK(savedOldRevision.ok());
|
|
CHECK(savedOldRevision.value().dirty());
|
|
CHECK_EQ(savedOldRevision.value().revision.saved, editOne.value().revision.edit);
|
|
CHECK_EQ(documents.content(id).value(), second);
|
|
|
|
auto currentTicket = documents.requestSave(id);
|
|
CHECK(documents.executeSave(currentTicket.value()).ok());
|
|
CHECK(!documents.snapshot(id).value().dirty());
|
|
CHECK(documents.close(id).ok());
|
|
|
|
DocumentService reopenedService(&store);
|
|
auto reopened = reopenedService.open(key, "Park", "content://World/Park.evscene");
|
|
CHECK(reopened.ok());
|
|
CHECK(reopenedService.content(reopened.value().id).value() == second);
|
|
}
|
|
|
|
TEST_CASE("editor.v2.document_save_detects_external_revision_conflict") {
|
|
MemoryAtomicDocumentStore store;
|
|
DocumentService documents(&store);
|
|
DocumentKey key{DocumentKind::Material, AssetGuid("material-asset")};
|
|
auto opened = documents.open(key, "Material", "content://Materials/Test.evmaterial");
|
|
const DocumentId id = opened.value().id;
|
|
CHECK(documents.edit(id, EditorValue("local-v1")).ok());
|
|
auto firstTicket = documents.requestSave(id);
|
|
CHECK(documents.executeSave(firstTicket.value()).ok());
|
|
|
|
CHECK(documents.edit(id, EditorValue("local-v2")).ok());
|
|
auto staleTicket = documents.requestSave(id);
|
|
const auto persisted = store.read("content://Materials/Test.evmaterial");
|
|
CHECK(store
|
|
.compareAndSwap("content://Materials/Test.evmaterial", 1, persisted.value().contentHash,
|
|
EditorValue("external-v2"))
|
|
.ok());
|
|
auto conflict = documents.executeSave(staleTicket.value());
|
|
CHECK_EQ(static_cast<int>(conflict.code()), static_cast<int>(EditorStatus::Conflict));
|
|
CHECK_EQ(static_cast<int>(conflict.value().state), static_cast<int>(DocumentState::Conflict));
|
|
}
|
|
|
|
TEST_CASE("editor.v2.import_place_save_and_reopen_headless_loop") {
|
|
MemoryAssetDatabase database;
|
|
ImportCoordinator imports(&database);
|
|
ImportProduct product;
|
|
product.record.guid = AssetGuid("asset-coaster-blueprint");
|
|
product.record.logicalUri = "content://Park/Coaster.blueprint";
|
|
product.record.typeId = "park.placeable";
|
|
product.record.sourceUri = "source://Coaster.blueprint";
|
|
product.record.importerId = "park.blueprint.importer";
|
|
CHECK(imports.publish(std::move(product)).ok());
|
|
AssetRecord placeable = database.find(AssetGuid("asset-coaster-blueprint")).value();
|
|
|
|
SceneDocumentTarget scene("park-scene");
|
|
LocalWorldAuthority authority(&scene);
|
|
LocalTransactionBackend transactions(&authority);
|
|
CreateSceneObjectRequest request;
|
|
request.id = ObjectId("placed-coaster");
|
|
request.name = placeable.guid.value();
|
|
request.transform = {10.0, 0.0, 20.0};
|
|
auto operation = ScenePlacementToolLogic().plan(scene, request);
|
|
CHECK(operation.ok());
|
|
TransactionSpec specification;
|
|
specification.id = TransactionId("place-imported-asset");
|
|
specification.label = "Place imported asset";
|
|
specification.target = TargetId(scene.targetId());
|
|
specification.baseRevision = scene.revision();
|
|
CHECK(transactions.begin(std::move(specification)).ok());
|
|
CHECK(transactions.append(operation.value()).ok());
|
|
CHECK(transactions.commit().ok());
|
|
|
|
MemoryAtomicDocumentStore store;
|
|
DocumentService documents(&store);
|
|
DocumentKey key{DocumentKind::Scene, AssetGuid("park-scene-asset")};
|
|
auto opened = documents.open(key, "Park Scene", "content://World/Park.evscene");
|
|
CHECK(documents.edit(opened.value().id, scene.snapshotValue()).ok());
|
|
auto ticket = documents.requestSave(opened.value().id);
|
|
CHECK(documents.executeSave(ticket.value()).ok());
|
|
const EditorValue savedScene = documents.content(opened.value().id).value();
|
|
CHECK(documents.close(opened.value().id).ok());
|
|
|
|
DocumentService reopened(&store);
|
|
auto reopenedDocument = reopened.open(key, "Park Scene", "content://World/Park.evscene");
|
|
CHECK(reopenedDocument.ok());
|
|
CHECK(reopened.content(reopenedDocument.value().id).value() == savedScene);
|
|
}
|
|
|
|
TEST_CASE("editor.v2.asset_database_indexes_and_pages_100k_records") {
|
|
MemoryAssetDatabase database;
|
|
constexpr int count = 100000;
|
|
for (int index = 0; index < count; ++index) {
|
|
AssetRecord record;
|
|
record.guid = AssetGuid("asset-" + std::to_string(index));
|
|
record.logicalUri = "content://Bulk/asset-" + std::to_string(index);
|
|
record.typeId = index % 2 == 0 ? "bulk.even" : "bulk.odd";
|
|
REQUIRE(database.publish(std::move(record)).ok());
|
|
}
|
|
CHECK_EQ(database.generation(), static_cast<std::uint64_t>(count));
|
|
AssetQuery query;
|
|
query.typeIds = {"bulk.odd"};
|
|
auto page = database.query(query, 1000, 128, database.generation());
|
|
REQUIRE(page.ok());
|
|
CHECK_EQ(page.value().values.size(), static_cast<std::size_t>(128));
|
|
CHECK(page.value().hasMore);
|
|
CHECK(database.findByUri("content://Bulk/asset-99999").ok());
|
|
}
|