#include #include #include #include #include #include #include #include #include #include #include #include using namespace mln; TEST(ImageManager, Missing) { auto imageManager = ImageManager::create(); EXPECT_FALSE(imageManager->getImage("doesnotexist")); } TEST(ImageManager, Basic) { FixtureLog log; auto imageManager = ImageManager::create(); auto images = parseSprite("default", util::read_file("test/fixtures/annotations/emerald.png"), util::read_file("test/fixtures/annotations/emerald.json")); for (auto& image : images) { imageManager->addImage(image); auto* stored = imageManager->getImage(image->id); ASSERT_TRUE(stored); EXPECT_EQ(image->image.size, stored->image.size); } imageManager->dumpDebugLogs(); } TEST(ImageManager, AddRemove) { FixtureLog log; auto imageManager = ImageManager::create(); imageManager->addImage(makeMutable("one", PremultipliedImage({16, 16}), 2.0f)); imageManager->addImage(makeMutable("two", PremultipliedImage({16, 16}), 2.0f)); imageManager->addImage(makeMutable("three", PremultipliedImage({16, 16}), 2.0f)); imageManager->removeImage("one"); imageManager->removeImage("two"); EXPECT_NE(nullptr, imageManager->getImage("three")); EXPECT_EQ(nullptr, imageManager->getImage("two")); EXPECT_EQ(nullptr, imageManager->getImage("four")); } TEST(ImageManager, Update) { FixtureLog log; auto imageManager = ImageManager::create(); imageManager->addImage(makeMutable("one", PremultipliedImage({16, 16}), 2.0f)); EXPECT_EQ(0, imageManager->updatedImageVersions.size()); imageManager->updateImage(makeMutable("one", PremultipliedImage({16, 16}), 2.0f)); EXPECT_EQ(1, imageManager->updatedImageVersions.size()); imageManager->removeImage("one"); EXPECT_EQ(0, imageManager->updatedImageVersions.size()); } TEST(ImageManager, RemoveReleasesBinPackRect) { FixtureLog log; auto imageManager = ImageManager::create(); imageManager->addImage(makeMutable("big", PremultipliedImage({32, 32}), 1.0f)); EXPECT_TRUE(imageManager->getImage("big")); imageManager->removeImage("big"); imageManager->addImage(makeMutable("big", PremultipliedImage({32, 32}), 1.0f)); EXPECT_TRUE(imageManager->getImage("big")); EXPECT_TRUE(log.empty()); } class StubImageRequestor : public ImageRequestor { public: StubImageRequestor(std::shared_ptr imageManager_) : ImageRequestor(imageManager_) {} void onImagesAvailable(ImageMap icons, ImageMap patterns, ImageVersionMap versionMap, uint64_t imageCorrelationID_) final { if (imagesAvailable && imageCorrelationID == imageCorrelationID_) imagesAvailable(icons, patterns, versionMap); } std::function imagesAvailable; uint64_t imageCorrelationID = 0; }; TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) { util::RunLoop runLoop; auto imageManager = ImageManager::create(); StubImageRequestor requestor(imageManager); bool notified = false; ImageManagerObserver observer; imageManager->setObserver(&observer); requestor.imagesAvailable = [&](ImageMap, ImageMap, ImageVersionMap) { notified = true; }; uint64_t imageCorrelationID = 0; ImageDependencies dependencies; dependencies.emplace("one", ImageType::Icon); imageManager->getImages(requestor, std::make_pair(dependencies, imageCorrelationID)); runLoop.runOnce(); ASSERT_FALSE(notified); imageManager->setLoaded(true); runLoop.runOnce(); ASSERT_FALSE(notified); imageManager->notifyIfMissingImageAdded(); runLoop.runOnce(); ASSERT_TRUE(notified); } TEST(ImageManager, NotifiesRequestorImmediatelyIfDependenciesAreSatisfied) { auto imageManager = ImageManager::create(); StubImageRequestor requestor(imageManager); bool notified = false; requestor.imagesAvailable = [&](ImageMap, ImageMap, ImageVersionMap) { notified = true; }; uint64_t imageCorrelationID = 0; ImageDependencies dependencies; dependencies.emplace("one", ImageType::Icon); imageManager->addImage(makeMutable("one", PremultipliedImage({16, 16}), 2.0f)); imageManager->getImages(requestor, std::make_pair(dependencies, imageCorrelationID)); ASSERT_TRUE(notified); } class StubImageManagerObserver : public ImageManagerObserver { public: int count = 0; std::function imageMissing = [](const std::string&) { }; void onStyleImageMissing(const std::string& id, const std::function& done) override { count++; imageMissing(id); done(); } std::function&)> removeUnusedStyleImages = [](const std::vector&) { }; void onRemoveUnusedStyleImages(const std::vector& unusedImageIDs) override { removeUnusedStyleImages(unusedImageIDs); } }; TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) { util::RunLoop runLoop; auto imageManager = ImageManager::create(); StubImageRequestor requestor(imageManager); StubImageManagerObserver observer; imageManager->setObserver(&observer); bool notified = false; requestor.imagesAvailable = [&](ImageMap, ImageMap, ImageVersionMap) { notified = true; }; uint64_t imageCorrelationID = 0; ImageDependencies dependencies; dependencies.emplace("pre", ImageType::Icon); imageManager->getImages(requestor, std::make_pair(dependencies, imageCorrelationID)); runLoop.runOnce(); EXPECT_EQ(observer.count, 0); ASSERT_FALSE(notified); imageManager->setLoaded(true); runLoop.runOnce(); EXPECT_EQ(observer.count, 1); ASSERT_FALSE(notified); imageManager->notifyIfMissingImageAdded(); runLoop.runOnce(); EXPECT_EQ(observer.count, 1); ASSERT_TRUE(notified); // Request for updated dependencies must be dispatched to the // observer. dependencies.emplace("post", ImageType::Icon); imageManager->getImages(requestor, std::make_pair(dependencies, ++imageCorrelationID)); ASSERT_TRUE(requestor.hasPendingRequests()); runLoop.runOnce(); ASSERT_FALSE(requestor.hasPendingRequests()); // Another requestor shall not have pending requests for already obtained images. StubImageRequestor anotherRequestor(imageManager); imageManager->getImages(anotherRequestor, std::make_pair(dependencies, ++imageCorrelationID)); ASSERT_FALSE(anotherRequestor.hasPendingRequests()); dependencies.emplace("unfamiliar", ImageType::Icon); imageManager->getImages(anotherRequestor, std::make_pair(dependencies, ++imageCorrelationID)); EXPECT_TRUE(anotherRequestor.hasPendingRequests()); EXPECT_TRUE(anotherRequestor.hasPendingRequest("unfamiliar")); } TEST(ImageManager, OnStyleImageMissingAfterSpriteLoaded) { util::RunLoop runLoop; auto imageManager = ImageManager::create(); StubImageRequestor requestor(imageManager); StubImageManagerObserver observer; imageManager->setObserver(&observer); bool notified = false; requestor.imagesAvailable = [&](ImageMap, ImageMap, ImageVersionMap) { notified = true; }; EXPECT_EQ(observer.count, 0); ASSERT_FALSE(notified); imageManager->setLoaded(true); runLoop.runOnce(); uint64_t imageCorrelationID = 0; ImageDependencies dependencies; dependencies.emplace("after", ImageType::Icon); imageManager->getImages(requestor, std::make_pair(dependencies, imageCorrelationID)); runLoop.runOnce(); EXPECT_EQ(observer.count, 1); ASSERT_FALSE(notified); imageManager->notifyIfMissingImageAdded(); runLoop.runOnce(); EXPECT_EQ(observer.count, 1); ASSERT_TRUE(notified); } TEST(ImageManager, RemoveUnusedStyleImages) { util::RunLoop runLoop; auto imageManager = ImageManager::create(); StubImageManagerObserver observer; imageManager->setObserver(&observer); imageManager->setLoaded(true); observer.imageMissing = [&imageManager](const std::string& id) { if (id == "1024px") { imageManager->addImage(makeMutable(id, PremultipliedImage({1024, 1024}), 1.0f)); } else { imageManager->addImage(makeMutable(id, PremultipliedImage({16, 16}), 1.0f)); } }; observer.removeUnusedStyleImages = [&imageManager](const std::vector& ids) { for (const auto& id : ids) { assert(imageManager->getImage(id)); imageManager->removeImage(id); } }; // Style sprite. imageManager->addImage(makeMutable("sprite", PremultipliedImage({16, 16}), 1.0f)); // Single requestor { std::unique_ptr requestor = std::make_unique(imageManager); imageManager->getImages(*requestor, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull)); runLoop.runOnce(); EXPECT_EQ(observer.count, 1); ASSERT_FALSE(imageManager->getImage("missing") == nullptr); } // Within cache limits, no need to notify client. imageManager->reduceMemoryUseIfCacheSizeExceedsLimit(); runLoop.runOnce(); ASSERT_FALSE(imageManager->getImage("missing") == nullptr); // Simulate OOM case, forces client notification to be issued. imageManager->reduceMemoryUse(); runLoop.runOnce(); ASSERT_TRUE(imageManager->getImage("missing") == nullptr); ASSERT_FALSE(imageManager->getImage("sprite") == nullptr); // Single requestor, exceed cache size limit. { std::unique_ptr requestor = std::make_unique(imageManager); imageManager->getImages(*requestor, std::make_pair(ImageDependencies{{"1024px", ImageType::Icon}}, 0ull)); runLoop.runOnce(); EXPECT_EQ(observer.count, 2); ASSERT_FALSE(imageManager->getImage("1024px") == nullptr); } // Over cache limits, need to notify client. imageManager->reduceMemoryUseIfCacheSizeExceedsLimit(); runLoop.runOnce(); ASSERT_TRUE(imageManager->getImage("1024px") == nullptr); ASSERT_FALSE(imageManager->getImage("sprite") == nullptr); // Multiple requestors { std::unique_ptr requestor1 = std::make_unique(imageManager); std::unique_ptr requestor2 = std::make_unique(imageManager); imageManager->getImages(*requestor1, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull)); imageManager->getImages(*requestor2, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 1ull)); runLoop.runOnce(); EXPECT_EQ(observer.count, 3); ASSERT_FALSE(imageManager->getImage("missing") == nullptr); } // Reduce memory usage and check that unused image was deleted when all requestors are destructed. imageManager->reduceMemoryUseIfCacheSizeExceedsLimit(); runLoop.runOnce(); ASSERT_FALSE(imageManager->getImage("missing") == nullptr); imageManager->reduceMemoryUse(); runLoop.runOnce(); ASSERT_TRUE(imageManager->getImage("missing") == nullptr); // Multiple requestors, check that image resource is not destroyed if there // is at least 1 requestor that uses it. std::unique_ptr requestor = std::make_unique(imageManager); { std::unique_ptr requestor1 = std::make_unique(imageManager); imageManager->getImages( *requestor, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}, {"1024px", ImageType::Icon}}, 0ull)); imageManager->getImages(*requestor1, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 1ull)); runLoop.runOnce(); EXPECT_EQ(observer.count, 5); ASSERT_FALSE(imageManager->getImage("missing") == nullptr); ASSERT_FALSE(imageManager->getImage("1024px") == nullptr); } // Reduce memory usage and check that requested image is not destructed. imageManager->reduceMemoryUseIfCacheSizeExceedsLimit(); runLoop.runOnce(); ASSERT_FALSE(imageManager->getImage("missing") == nullptr); ASSERT_FALSE(imageManager->getImage("1024px") == nullptr); // Release last requestor and check if resource was released when cache size is over the limit. requestor.reset(); imageManager->reduceMemoryUseIfCacheSizeExceedsLimit(); runLoop.runOnce(); ASSERT_TRUE(imageManager->getImage("missing") == nullptr); ASSERT_TRUE(imageManager->getImage("1024px") == nullptr); ASSERT_FALSE(imageManager->getImage("sprite") == nullptr); }