diff --git a/src/Resource.h b/src/Resource.h index 484036726..33085cb7c 100644 --- a/src/Resource.h +++ b/src/Resource.h @@ -33,11 +33,23 @@ namespace Magnum { * @see Resource::state(), ResourceManager::state() */ enum class ResourceState: std::uint8_t { - /** The resource is not yet loaded. */ + /** The resource is not yet loaded (and no fallback is available). */ NotLoaded, /** The resource is not yet loaded and fallback resource is used instead. */ - Fallback, + NotLoadedFallback, + + /** The resource is currently loading (and no fallback is available). */ + Loading, + + /** The resource is currently loading and fallback resource is used instead. */ + LoadingFallback, + + /** The resource was not found (and no fallback is available). */ + NotFound, + + /** The resource was not found and fallback resource is used instead. */ + NotFoundFallback, /** The resource is loaded, but can be changed by the manager at any time. */ Mutable, @@ -157,7 +169,7 @@ class Resource { /** * @brief %Resource state * - * @see operator bool() + * @see operator bool(), ResourceManager::state() */ inline ResourceState state() { acquire(); @@ -166,10 +178,11 @@ class Resource { /** * @brief Whether the resource is available - * @return False when resource is not loaded and no fallback is - * available, true otherwise. * - * @see state() + * Returns `false` when resource is not loaded and no fallback is + * available (i.e. state() is either @ref ResourceState "ResourceState::NotLoaded", + * @ref ResourceState "ResourceState::Loading" or + * @ref ResourceState "ResourceState::NotFound"), true otherwise. */ inline operator bool() { acquire(); @@ -219,12 +232,23 @@ class Resource { lastCheck = manager->lastChange(); /* Try to get the data */ - if((data = d.data)) - _state = static_cast(d.state); - else if((data = manager->fallback())) - _state = ResourceState::Fallback; - else - _state = ResourceState::NotLoaded; + data = d.data; + _state = static_cast(d.state); + + /* Data are not available */ + if(!data) { + /* Fallback found, add *Fallback to state */ + if((data = manager->fallback())) { + if(_state == ResourceState::Loading) + _state = ResourceState::LoadingFallback; + else if(_state == ResourceState::NotFound) + _state = ResourceState::NotFoundFallback; + else _state = ResourceState::NotLoadedFallback; + + /* Fallback not found and loading didn't start yet */ + } else if(_state != ResourceState::Loading && _state != ResourceState::NotFound) + _state = ResourceState::NotLoaded; + } } Implementation::ResourceManagerData* manager; diff --git a/src/ResourceManager.h b/src/ResourceManager.h index b11142eda..22fe1c0b0 100644 --- a/src/ResourceManager.h +++ b/src/ResourceManager.h @@ -31,6 +31,18 @@ namespace Magnum { * @see ResourceManager::set(), ResourceState */ enum class ResourceDataState: std::uint8_t { + /** + * The resource is currently loading. Parameter @p data in ResourceManager::set() + * should be set to `null`. + */ + Loading = int(ResourceState::Loading), + + /** + * The resource was not found. Parameter @p data in ResourceManager::set() + * should be set to `null`. + */ + NotFound = int(ResourceState::NotFound), + /** * The resource can be changed by the manager in the future. This is * slower, as Resource needs to ask the manager for new version every time @@ -99,10 +111,25 @@ namespace Implementation { ResourceState state(ResourceKey key) const { auto it = _data.find(key); - if(it == _data.end() || !it->second.data) - return _fallback ? ResourceState::Fallback : ResourceState::NotLoaded; - else - return static_cast(it->second.state); + + /* Resource not loaded */ + if(it == _data.end() || !it->second.data) { + /* Fallback found, add *Fallback to state */ + if(_fallback) { + if(it != _data.end() && it->second.state == ResourceDataState::Loading) + return ResourceState::LoadingFallback; + else if(it != _data.end() && it->second.state == ResourceDataState::NotFound) + return ResourceState::NotFoundFallback; + else return ResourceState::NotLoadedFallback; + } + + /* Fallback not found, loading didn't start yet */ + if(it == _data.end() || (it->second.state != ResourceDataState::Loading && it->second.state != ResourceDataState::NotFound)) + return ResourceState::NotLoaded; + } + + /* Loading / NotFound without fallback, Mutable / Final */ + return static_cast(it->second.state); } template inline Resource get(ResourceKey key) { @@ -112,9 +139,13 @@ namespace Implementation { void set(ResourceKey key, T* data, ResourceDataState state, ResourcePolicy policy) { auto it = _data.find(key); + /* NotFound / Loading state shouldn't have any data */ + CORRADE_ASSERT((data == nullptr) == (state == ResourceDataState::NotFound || state == ResourceDataState::Loading), + "ResourceManager::set(): data should be null if and only if state is NotFound or Loading", ); + /* Cannot change resource with already final state */ CORRADE_ASSERT(it == _data.end() || it->second.state != ResourceDataState::Final, - "ResourceManager: cannot change already final resource", ); + "ResourceManager::set(): cannot change already final resource", ); /* If nothing is referencing reference-counted resource, we're done */ if(policy == ResourcePolicy::ReferenceCounted && (it == _data.end() || it->second.referenceCount == 0)) { diff --git a/src/Test/ResourceManagerTest.cpp b/src/Test/ResourceManagerTest.cpp index 9c50d214b..863a5d2c1 100644 --- a/src/Test/ResourceManagerTest.cpp +++ b/src/Test/ResourceManagerTest.cpp @@ -40,6 +40,9 @@ size_t Data::count = 0; ResourceManagerTest::ResourceManagerTest() { addTests(&ResourceManagerTest::state, + &ResourceManagerTest::stateFallback, + &ResourceManagerTest::stateDisallowed, + &ResourceManagerTest::basic, &ResourceManagerTest::residentPolicy, &ResourceManagerTest::referenceCountedPolicy, &ResourceManagerTest::manualPolicy); @@ -48,19 +51,84 @@ ResourceManagerTest::ResourceManagerTest() { void ResourceManagerTest::state() { ResourceManager rm; - ResourceKey questionKey("the-question"); - rm.set(questionKey, new int32_t(10), ResourceDataState::Mutable, ResourcePolicy::Resident); - Resource theQuestion = rm.get(questionKey); - CORRADE_COMPARE(theQuestion.state(), ResourceState::Mutable); - CORRADE_COMPARE(*theQuestion, 10); + Resource data = rm.get("data"); + CORRADE_VERIFY(!data); + CORRADE_COMPARE(data.state(), ResourceState::NotLoaded); + CORRADE_COMPARE(rm.state("data"), ResourceState::NotLoaded); + + rm.set("data", nullptr, ResourceDataState::Loading, ResourcePolicy::Resident); + CORRADE_VERIFY(!data); + CORRADE_COMPARE(data.state(), ResourceState::Loading); + CORRADE_COMPARE(rm.state("data"), ResourceState::Loading); + + rm.set("data", nullptr, ResourceDataState::NotFound, ResourcePolicy::Resident); + CORRADE_VERIFY(!data); + CORRADE_COMPARE(data.state(), ResourceState::NotFound); + CORRADE_COMPARE(rm.state("data"), ResourceState::NotFound); + + /* Nothing happened at all */ + CORRADE_COMPARE(Data::count, 0); +} + +void ResourceManagerTest::stateFallback() { + { + ResourceManager rm; + rm.setFallback(new Data); + + Resource data = rm.get("data"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data.state(), ResourceState::NotLoadedFallback); + CORRADE_COMPARE(rm.state("data"), ResourceState::NotLoadedFallback); + + rm.set("data", nullptr, ResourceDataState::Loading, ResourcePolicy::Resident); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data.state(), ResourceState::LoadingFallback); + CORRADE_COMPARE(rm.state("data"), ResourceState::LoadingFallback); + + rm.set("data", nullptr, ResourceDataState::NotFound, ResourcePolicy::Resident); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data.state(), ResourceState::NotFoundFallback); + CORRADE_COMPARE(rm.state("data"), ResourceState::NotFoundFallback); + + /* Only fallback is here */ + CORRADE_COMPARE(Data::count, 1); + } + + /* Fallback gets destroyed */ + CORRADE_COMPARE(Data::count, 0); +} + +void ResourceManagerTest::stateDisallowed() { + ResourceManager rm; + + stringstream out; + Error::setOutput(&out); + + Data d; + rm.set("data", &d, ResourceDataState::Loading, ResourcePolicy::Resident); + CORRADE_COMPARE(out.str(), "ResourceManager::set(): data should be null if and only if state is NotFound or Loading\n"); + + out.str(""); + rm.set("data", nullptr, ResourceDataState::Final, ResourcePolicy::Resident); + CORRADE_COMPARE(out.str(), "ResourceManager::set(): data should be null if and only if state is NotFound or Loading\n"); +} - /* Check that hash function is working properly */ +void ResourceManagerTest::basic() { + ResourceManager rm; + + /* One mutable, one final */ + ResourceKey questionKey("the-question"); ResourceKey answerKey("the-answer"); + rm.set(questionKey, new int32_t(10), ResourceDataState::Mutable, ResourcePolicy::Resident); rm.set(answerKey, new int32_t(42), ResourceDataState::Final, ResourcePolicy::Resident); + Resource theQuestion = rm.get(questionKey); Resource theAnswer = rm.get(answerKey); + + /* Check basic functionality */ + CORRADE_COMPARE(theQuestion.state(), ResourceState::Mutable); CORRADE_COMPARE(theAnswer.state(), ResourceState::Final); + CORRADE_COMPARE(*theQuestion, 10); CORRADE_COMPARE(*theAnswer, 42); - CORRADE_COMPARE(rm.count(), 2); /* Cannot change already final resource */ @@ -68,9 +136,9 @@ void ResourceManagerTest::state() { Error::setOutput(&out); rm.set(answerKey, new int32_t(43), ResourceDataState::Mutable, ResourcePolicy::Resident); CORRADE_COMPARE(*theAnswer, 42); - CORRADE_COMPARE(out.str(), "ResourceManager: cannot change already final resource\n"); + CORRADE_COMPARE(out.str(), "ResourceManager::set(): cannot change already final resource\n"); - /* Check non-final resource changes */ + /* But non-final can be changed */ rm.set(questionKey, new int32_t(20), ResourceDataState::Final, ResourcePolicy::Resident); CORRADE_COMPARE(theQuestion.state(), ResourceState::Final); CORRADE_COMPARE(*theQuestion, 20); diff --git a/src/Test/ResourceManagerTest.h b/src/Test/ResourceManagerTest.h index b6f387498..73b4a49ea 100644 --- a/src/Test/ResourceManagerTest.h +++ b/src/Test/ResourceManagerTest.h @@ -24,6 +24,9 @@ class ResourceManagerTest: public Corrade::TestSuite::Tester