Add test for async schema loading.

This commit is contained in:
Kevin Ring 2023-08-28 18:24:08 +10:00
parent 6145b547a6
commit 51e798e4ea
7 changed files with 308 additions and 18 deletions

View File

@ -494,9 +494,6 @@ private:
// scratch variable so that it can allocate only when growing bigger.
std::vector<const TileOcclusionRendererProxy*> _childOcclusionProxies;
CesiumAsync::Promise<void> _rootTileAvailablePromise;
CesiumAsync::SharedFuture<void> _rootTileAvailableFuture;
CesiumUtility::IntrusivePointer<TilesetContentManager>
_pTilesetContentManager;

View File

@ -43,9 +43,6 @@ Tileset::Tileset(
_previousFrameNumber(0),
_distances(),
_childOcclusionProxies(),
_rootTileAvailablePromise{externals.asyncSystem.createPromise<void>()},
_rootTileAvailableFuture{
this->_rootTileAvailablePromise.getFuture().share()},
_pTilesetContentManager{new TilesetContentManager(
_externals,
_options,
@ -64,9 +61,6 @@ Tileset::Tileset(
_previousFrameNumber(0),
_distances(),
_childOcclusionProxies(),
_rootTileAvailablePromise{externals.asyncSystem.createPromise<void>()},
_rootTileAvailableFuture{
this->_rootTileAvailablePromise.getFuture().share()},
_pTilesetContentManager{new TilesetContentManager(
_externals,
_options,
@ -85,9 +79,6 @@ Tileset::Tileset(
_previousFrameNumber(0),
_distances(),
_childOcclusionProxies(),
_rootTileAvailablePromise{externals.asyncSystem.createPromise<void>()},
_rootTileAvailableFuture{
this->_rootTileAvailablePromise.getFuture().share()},
_pTilesetContentManager{new TilesetContentManager(
_externals,
_options,
@ -108,7 +99,7 @@ CesiumAsync::SharedFuture<void>& Tileset::getAsyncDestructionCompleteEvent() {
}
CesiumAsync::SharedFuture<void>& Tileset::getRootTileAvailableEvent() {
return this->_rootTileAvailableFuture;
return this->_pTilesetContentManager->getRootTileAvailableEvent();
}
const std::vector<Credit>& Tileset::getTilesetCredits() const noexcept {

View File

@ -599,7 +599,12 @@ TilesetContentManager::TilesetContentManager(
_tilesDataUsed{0},
_destructionCompletePromise{externals.asyncSystem.createPromise<void>()},
_destructionCompleteFuture{
this->_destructionCompletePromise.getFuture().share()} {}
this->_destructionCompletePromise.getFuture().share()},
_rootTileAvailablePromise{externals.asyncSystem.createPromise<void>()},
_rootTileAvailableFuture{
this->_rootTileAvailablePromise.getFuture().share()} {
this->_rootTileAvailablePromise.resolve();
}
TilesetContentManager::TilesetContentManager(
const TilesetExternals& externals,
@ -623,7 +628,10 @@ TilesetContentManager::TilesetContentManager(
_tilesDataUsed{0},
_destructionCompletePromise{externals.asyncSystem.createPromise<void>()},
_destructionCompleteFuture{
this->_destructionCompletePromise.getFuture().share()} {
this->_destructionCompletePromise.getFuture().share()},
_rootTileAvailablePromise{externals.asyncSystem.createPromise<void>()},
_rootTileAvailableFuture{
this->_rootTileAvailablePromise.getFuture().share()} {
if (!url.empty()) {
this->notifyTileStartLoading(nullptr);
@ -721,13 +729,16 @@ TilesetContentManager::TilesetContentManager(
TilesetLoadType::TilesetJson,
errorCallback,
std::move(result));
thiz->_rootTileAvailablePromise.resolve();
})
.catchInMainThread([thiz](std::exception&& e) {
thiz->notifyTileDoneLoading(nullptr);
SPDLOG_LOGGER_ERROR(
thiz->_externals.pLogger,
"An unexpected error occurs when loading tile: {}",
"An unexpected error occurred when loading tile: {}",
e.what());
thiz->_rootTileAvailablePromise.reject(
std::runtime_error("Root tile failed to load."));
});
}
}
@ -756,7 +767,10 @@ TilesetContentManager::TilesetContentManager(
_tilesDataUsed{0},
_destructionCompletePromise{externals.asyncSystem.createPromise<void>()},
_destructionCompleteFuture{
this->_destructionCompletePromise.getFuture().share()} {
this->_destructionCompletePromise.getFuture().share()},
_rootTileAvailablePromise{externals.asyncSystem.createPromise<void>()},
_rootTileAvailableFuture{
this->_rootTileAvailablePromise.getFuture().share()} {
if (ionAssetID > 0) {
auto authorizationChangeListener = [this](
const std::string& header,
@ -793,13 +807,16 @@ TilesetContentManager::TilesetContentManager(
TilesetLoadType::CesiumIon,
errorCallback,
std::move(result));
thiz->_rootTileAvailablePromise.resolve();
})
.catchInMainThread([thiz](std::exception&& e) {
thiz->notifyTileDoneLoading(nullptr);
SPDLOG_LOGGER_ERROR(
thiz->_externals.pLogger,
"An unexpected error occurs when loading tile: {}",
"An unexpected error occurred when loading tile: {}",
e.what());
thiz->_rootTileAvailablePromise.reject(
std::runtime_error("Root tile failed to load."));
});
}
}
@ -809,6 +826,11 @@ TilesetContentManager::getAsyncDestructionCompleteEvent() {
return this->_destructionCompleteFuture;
}
CesiumAsync::SharedFuture<void>&
TilesetContentManager::getRootTileAvailableEvent() {
return this->_rootTileAvailableFuture;
}
TilesetContentManager::~TilesetContentManager() noexcept {
assert(this->_tileLoadsInProgress == 0);
this->unloadAll();

View File

@ -51,6 +51,13 @@ public:
*/
CesiumAsync::SharedFuture<void>& getAsyncDestructionCompleteEvent();
/**
* @brief A future that resolves when the details of the root tile of this
* tileset are available. The root tile's content (e.g., 3D model), however,
* will not necessarily be loaded yet.
*/
CesiumAsync::SharedFuture<void>& getRootTileAvailableEvent();
~TilesetContentManager() noexcept;
void loadTileContent(Tile& tile, const TilesetOptions& tilesetOptions);
@ -146,5 +153,8 @@ private:
CesiumAsync::Promise<void> _destructionCompletePromise;
CesiumAsync::SharedFuture<void> _destructionCompleteFuture;
CesiumAsync::Promise<void> _rootTileAvailablePromise;
CesiumAsync::SharedFuture<void> _rootTileAvailableFuture;
};
} // namespace Cesium3DTilesSelection

View File

@ -1312,6 +1312,186 @@ TEST_CASE("Allows access to material variants") {
CHECK(variantsByGroup == expected);
}
TEST_CASE("Allows access to material variants in an external schema") {
Cesium3DTilesSelection::registerAllTileContentTypes();
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
testDataPath = testDataPath / "MaterialVariants";
std::vector<std::string> files{
"tileset-external-schema.json",
"schema.json",
"parent.b3dm"};
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
mockCompletedRequests;
for (const auto& file : files) {
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
std::make_unique<SimpleAssetResponse>(
static_cast<uint16_t>(200),
"doesn't matter",
CesiumAsync::HttpHeaders{},
readFile(testDataPath / file));
mockCompletedRequests.insert(
{file,
std::make_shared<SimpleAssetRequest>(
"GET",
file,
CesiumAsync::HttpHeaders{},
std::move(mockCompletedResponse))});
}
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
TilesetExternals tilesetExternals{
mockAssetAccessor,
std::make_shared<SimplePrepareRendererResource>(),
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
nullptr};
Tileset tileset(tilesetExternals, "tileset-external-schema.json");
// getMetadata returns nullptr before the root tile is loaded.
CHECK(tileset.getMetadata() == nullptr);
bool wasCalled = false;
tileset.loadMetadata().thenInMainThread(
[&wasCalled](const TilesetMetadata* pMetadata) {
wasCalled = true;
REQUIRE(pMetadata);
REQUIRE(pMetadata->schema);
REQUIRE(pMetadata->metadata);
std::optional<Cesium3DTiles::FoundMetadataProperty> found1 =
Cesium3DTiles::MetadataQuery::findFirstPropertyWithSemantic(
*pMetadata->schema,
*pMetadata->metadata,
"MATERIAL_VARIANTS");
REQUIRE(found1);
CHECK(found1->classIdentifier == "MaterialVariants");
CHECK(found1->classDefinition.properties.size() == 1);
CHECK(found1->propertyIdentifier == "material_variants");
CHECK(
found1->propertyDefinition.description ==
"Names of material variants to be expected in the glTF assets");
REQUIRE(found1->propertyValue.isArray());
const JsonValue::Array& variantsJson = found1->propertyValue.getArray();
std::vector<std::string> variants(variantsJson.size());
std::transform(
variantsJson.begin(),
variantsJson.end(),
variants.begin(),
[](const JsonValue& value) {
return value.getStringOrDefault("");
});
REQUIRE(variants.size() == 4);
CHECK(variants[0] == "RGB");
CHECK(variants[1] == "RRR");
CHECK(variants[2] == "GGG");
CHECK(variants[3] == "BBB");
std::vector<std::vector<std::string>> variantsByGroup;
for (const Cesium3DTiles::GroupMetadata& group : pMetadata->groups) {
std::vector<std::string>& groupVariants =
variantsByGroup.emplace_back();
std::optional<Cesium3DTiles::FoundMetadataProperty> found2 =
Cesium3DTiles::MetadataQuery::findFirstPropertyWithSemantic(
*pMetadata->schema,
group,
"MATERIAL_VARIANTS");
REQUIRE(found2);
REQUIRE(found2->propertyValue.isArray());
const JsonValue::Array& groupVariantsJson =
found2->propertyValue.getArray();
groupVariants.reserve(groupVariantsJson.size());
for (size_t i = 0; i < groupVariantsJson.size(); ++i) {
groupVariants.emplace_back(
groupVariantsJson[i].getStringOrDefault(""));
}
}
std::vector<std::vector<std::string>> expected = {
{"RGB", "RRR"},
{"GGG", "BBB"}};
CHECK(variantsByGroup == expected);
});
CHECK(!wasCalled);
initializeTileset(tileset);
CHECK(wasCalled);
}
TEST_CASE("Future from loadSchema rejects if schemaUri can't be loaded") {
Cesium3DTilesSelection::registerAllTileContentTypes();
std::filesystem::path testDataPath = Cesium3DTilesSelection_TEST_DATA_DIR;
testDataPath = testDataPath / "MaterialVariants";
std::vector<std::string> files{"tileset-external-schema.json", "parent.b3dm"};
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>
mockCompletedRequests;
for (const auto& file : files) {
std::unique_ptr<SimpleAssetResponse> mockCompletedResponse =
std::make_unique<SimpleAssetResponse>(
static_cast<uint16_t>(200),
"doesn't matter",
CesiumAsync::HttpHeaders{},
readFile(testDataPath / file));
mockCompletedRequests.insert(
{file,
std::make_shared<SimpleAssetRequest>(
"GET",
file,
CesiumAsync::HttpHeaders{},
std::move(mockCompletedResponse))});
}
mockCompletedRequests.insert(
{"schema.json",
std::make_shared<SimpleAssetRequest>(
"GET",
"schema.json",
CesiumAsync::HttpHeaders{},
std::make_unique<SimpleAssetResponse>(
404,
"doesn't matter",
CesiumAsync::HttpHeaders{},
std::vector<std::byte>()))});
std::shared_ptr<SimpleAssetAccessor> mockAssetAccessor =
std::make_shared<SimpleAssetAccessor>(std::move(mockCompletedRequests));
TilesetExternals tilesetExternals{
mockAssetAccessor,
std::make_shared<SimplePrepareRendererResource>(),
AsyncSystem(std::make_shared<SimpleTaskProcessor>()),
nullptr};
Tileset tileset(tilesetExternals, "tileset-external-schema.json");
// getMetadata returns nullptr before the root tile is loaded.
CHECK(tileset.getMetadata() == nullptr);
bool wasResolved = false;
bool wasRejected = false;
tileset.loadMetadata()
.thenInMainThread(
[&wasResolved](const TilesetMetadata*) { wasResolved = true; })
.catchInMainThread([&wasRejected](const std::exception& exception) {
CHECK(std::string(exception.what()).find("") != std::string::npos);
wasRejected = true;
});
CHECK(!wasResolved);
CHECK(!wasRejected);
initializeTileset(tileset);
CHECK(!wasResolved);
CHECK(wasRejected);
}
namespace {
void runUnconditionallyRefinedTestCase(const TilesetOptions& options) {

View File

@ -0,0 +1,14 @@
{
"classes": {
"MaterialVariants": {
"properties": {
"material_variants": {
"type": "STRING",
"array": true,
"description": "Names of material variants to be expected in the glTF assets",
"semantic": "MATERIAL_VARIANTS"
}
}
}
}
}

View File

@ -0,0 +1,76 @@
{
"asset": {
"version": "1.0",
"tilesetVersion": "1.2.3"
},
"extras": {
"name": "Sample Tileset"
},
"properties": {
"id": {
"minimum": 0,
"maximum": 9
},
"Longitude": {
"minimum": -1.3197192952275933,
"maximum": -1.319644104024109
},
"Latitude": {
"minimum": 0.698848878034009,
"maximum": 0.6989046192460953
},
"Height": {
"minimum": 6.161747192963958,
"maximum": 85.41026367992163
}
},
"schemaUri": "schema.json",
"metadata": {
"class": "MaterialVariants",
"properties": {
"material_variants": ["RGB", "RRR", "GGG", "BBB"]
}
},
"groups": [
{
"class": "MaterialVariants",
"properties": {
"material_variants": ["RGB", "RRR"]
}
},
{
"class": "MaterialVariants",
"properties": {
"material_variants": ["GGG", "BBB"]
}
}
],
"geometricError": 240,
"root": {
"boundingVolume": {
"region": [
-1.3197209591796106,
0.6988424218,
-1.3196390408203893,
0.6989055782,
0,
88
]
},
"geometricError": 70,
"refine": "ADD",
"content": {
"uri": "parent.b3dm",
"boundingVolume": {
"region": [
-1.3197004795898053,
0.6988582109,
-1.3196595204101946,
0.6988897891,
0,
88
]
}
}
}
}