Files
OpenSpace/src/scene/asset.cpp
2017-12-15 13:21:10 +01:00

862 lines
25 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2017 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <openspace/scene/asset.h>
#include <openspace/scene/assetloader.h>
#include <ghoul/logging/logmanager.h>
#include <algorithm>
#include <unordered_set>
namespace {
const char* _loggerCat = "Asset";
float syncProgress(std::vector<std::shared_ptr<openspace::Asset>> assets) {
size_t nTotalBytes = 0;
size_t nSyncedBytes = 0;
for (const auto& a : assets) {
const std::vector<std::shared_ptr<openspace::ResourceSynchronization>> syncs =
a->ownSynchronizations();
for (const auto& sync : syncs) {
if (sync->nTotalBytesIsKnown()) {
nTotalBytes += sync->nTotalBytes();
nSyncedBytes += sync->nSynchronizedBytes();
} else if (sync->isSyncing()) {
// A resource is still synchronizing but its size is unknown.
// Impossible to know the global progress.
return 0;
}
}
}
if (nTotalBytes == 0) {
return 0.f;
}
return static_cast<float>(nSyncedBytes) / static_cast<float>(nTotalBytes);
}
}
namespace openspace {
Asset::Asset(AssetLoader* loader, SynchronizationWatcher* syncWatcher)
: _state(State::SyncResolved)
, _loader(loader)
, _synchronizationWatcher(syncWatcher)
, _hasAssetPath(false)
, _assetName("Root Asset")
{}
Asset::Asset(AssetLoader* loader,
SynchronizationWatcher* syncWatcher,
ghoul::filesystem::File assetPath
)
: _state(State::Unloaded)
, _loader(loader)
, _synchronizationWatcher(syncWatcher)
, _hasAssetPath(true)
, _assetPath(assetPath)
{}
Asset::~Asset() {
for (const SynchronizationWatcher::WatchHandle& h : _syncWatches) {
_synchronizationWatcher->unwatchSynchronization(h);
}
_loader->untrackAsset(this);
}
std::string Asset::resolveLocalResource(std::string resourceName) {
std::string currentAssetDirectory = assetDirectory();
return currentAssetDirectory +
ghoul::filesystem::FileSystem::PathSeparator +
resourceName;
}
Asset::State Asset::state() const {
return _state;
}
void Asset::setState(Asset::State state) {
if (_state == state) {
return;
}
_state = state;
std::shared_ptr<Asset> thisAsset = shared_from_this();
_loader->assetStateChanged(thisAsset, state);
for (auto& requiringAsset : _requiringAssets) {
if (std::shared_ptr<Asset> a = requiringAsset.lock()) {
a->requiredAssetChangedState(thisAsset);
}
}
for (auto& requestingAsset : _requestingAssets) {
if (std::shared_ptr<Asset> a = requestingAsset.lock()) {
a->requestedAssetChangedState(thisAsset);
}
}
}
void Asset::requiredAssetChangedState(std::shared_ptr<Asset> child) {
ghoul_assert(!isInitialized(),
"Required asset changing state while parent asset is initialized");
State childState = child->state();
if (childState == State::SyncResolved) {
if (isSyncResolveReady()) {
setState(State::SyncResolved);
}
} else if (childState == State::SyncRejected) {
setState(State::SyncRejected);
}
}
void Asset::requestedAssetChangedState(std::shared_ptr<Asset> child) {
State childState = child->state();
if (child->hasInitializedParent()) {
if (childState == State::Loaded) {
child->startSynchronizations();
}
if (childState == State::SyncResolved) {
child->initialize();
}
}
}
void Asset::addSynchronization(std::shared_ptr<ResourceSynchronization> synchronization) {
_synchronizations.push_back(synchronization);
// Set up callback for synchronization state change
// The synchronization watcher will make sure that callbacks are invoked in the main thread.
SynchronizationWatcher::WatchHandle watch = _synchronizationWatcher->watchSynchronization(
synchronization,
[this, synchronization](ResourceSynchronization::State state) {
syncStateChanged(synchronization, state);
}
);
_syncWatches.push_back(watch);
}
void Asset::syncStateChanged(std::shared_ptr<ResourceSynchronization> synchronization,
ResourceSynchronization::State state)
{
ghoul_assert(!isInitialized(),
"Synchronization changing state while asset is initialized");
if (state == ResourceSynchronization::State::Resolved) {
if (!isSynchronized() && isSyncResolveReady()) {
setState(State::SyncResolved);
}
} else if (state == ResourceSynchronization::State::Rejected) {
setState(State::SyncRejected);
}
}
bool Asset::isSyncResolveReady() {
std::vector<std::shared_ptr<Asset>> requiredAssets = this->requiredAssets();
auto pendingRequiredAsset = std::find_if(
requiredAssets.begin(),
requiredAssets.end(),
[](std::shared_ptr<Asset>& a) {
return a->state() == Asset::State::Loaded ||
a->state() == Asset::State::Synchronizing;
}
);
if (pendingRequiredAsset != requiredAssets.end()) {
// Not considered resolved if all children are not resolved
return false;
}
std::vector<std::shared_ptr<ResourceSynchronization>> syncs =
this->ownSynchronizations();
auto unresolvedOwnSynchronization = std::find_if(
syncs.begin(),
syncs.end(),
[](std::shared_ptr<ResourceSynchronization>& s) {
return !s->isResolved();
}
);
// To be considered resolved, all own synchronizations need to be resolved
return unresolvedOwnSynchronization == syncs.end();
}
std::vector<std::shared_ptr<ResourceSynchronization>> Asset::ownSynchronizations() const {
return _synchronizations;
}
std::vector<std::shared_ptr<Asset>> Asset::subTreeAssets() {
std::unordered_set<std::shared_ptr<Asset>> assets({ shared_from_this() });
for (auto& c : childAssets()) {
std::vector<std::shared_ptr<Asset>> subTree = c->subTreeAssets();
std::copy(subTree.begin(), subTree.end(), std::inserter(assets, assets.end()));
}
std::vector<std::shared_ptr<Asset>> assetVector(assets.begin(), assets.end());
return assetVector;
}
std::vector<std::shared_ptr<Asset>> Asset::requiredSubTreeAssets() {
std::unordered_set<std::shared_ptr<Asset>> assets({ shared_from_this() });
for (const auto& dep : _requiredAssets) {
std::vector<std::shared_ptr<Asset>> subTree = dep->requiredSubTreeAssets();
std::copy(subTree.begin(), subTree.end(), std::inserter(assets, assets.end()));
}
std::vector<std::shared_ptr<Asset>> assetVector(assets.begin(), assets.end());
return assetVector;
}
bool Asset::isLoaded() const {
State s = state();
return s != State::Unloaded &&
s != State::LoadingFailed;
}
bool Asset::isSynchronized() const {
State s = state();
return s == State::SyncResolved ||
s == State::Initialized ||
s == State::InitializationFailed;
}
bool Asset::isSyncingOrResolved() const {
State s = state();
return s == State::Synchronizing ||
s == State::SyncResolved ||
s == State::Initialized ||
s == State::InitializationFailed;
}
bool Asset::hasLoadedParent() const {
for (const auto& p : _requiringAssets) {
std::shared_ptr<Asset> parent = p.lock();
if (!parent) {
continue;
}
if (parent->isLoaded()) {
return true;
}
}
for (const auto& p : _requestingAssets) {
std::shared_ptr<Asset> parent = p.lock();
if (!parent) {
continue;
}
if (parent->isLoaded()) {
return true;
}
if (parent->hasLoadedParent()) {
return true;
}
}
return false;
}
bool Asset::hasSyncingOrResolvedParent() const {
for (const auto& p : _requiringAssets) {
std::shared_ptr<Asset> parent = p.lock();
if (!parent) {
continue;
}
if (parent->isSyncingOrResolved()) {
return true;
}
}
for (const auto& p : _requestingAssets) {
std::shared_ptr<Asset> parent = p.lock();
if (!parent) {
continue;
}
if (parent->isSyncingOrResolved()) {
return true;
}
if (parent->hasSyncingOrResolvedParent()) {
return true;
}
}
return false;
}
bool Asset::hasInitializedParent() const {
for (const auto& p : _requiringAssets) {
std::shared_ptr<Asset> parent = p.lock();
if (!parent) {
continue;
}
if (parent->isInitialized()) {
return true;
}
}
for (const auto& p : _requestingAssets) {
std::shared_ptr<Asset> parent = p.lock();
if (!parent) {
continue;
}
if (parent->isInitialized()) {
return true;
}
if (parent->hasInitializedParent()) {
return true;
}
}
return false;
}
bool Asset::isInitialized() const {
State s = state();
return s == State::Initialized;
}
bool Asset::startSynchronizations() {
// Do not attempt to resync if this is already initialized
if (state() == State::Initialized) {
return false;
}
setState(State::Synchronizing);
bool foundUnresolved = false;
// Start synchronization of all children first.
for (auto& child : requiredAssets()) {
if (child->startSynchronizations()) {
foundUnresolved = true;
}
}
for (auto& child : requestedAssets()) {
child->startSynchronizations();
}
// Now synchronize its own synchronizations.
for (const auto& s : ownSynchronizations()) {
if (!s->isResolved()) {
foundUnresolved = true;
s->start();
}
}
// If all syncs are resolved (or no syncs exist), mark as resolved.
if (!foundUnresolved) {
setState(State::SyncResolved);
}
return foundUnresolved;
}
bool Asset::cancelAllSynchronizations() {
bool cancelledAnySync = false;
for (auto& child : childAssets()) {
bool cancelled = child->cancelAllSynchronizations();
if (cancelled) {
cancelledAnySync = true;
}
}
for (const auto& s : ownSynchronizations()) {
if (s->isSyncing()) {
cancelledAnySync = true;
s->cancel();
setState(State::Loaded);
}
}
if (cancelledAnySync) {
setState(State::Loaded);
}
return cancelledAnySync;
}
bool Asset::cancelUnwantedSynchronizations() {
if (hasSyncingOrResolvedParent()) {
return false;
}
bool cancelledAnySync = false;
for (auto& child : childAssets()) {
bool cancelled = child->cancelUnwantedSynchronizations();
if (cancelled) {
cancelledAnySync = true;
}
}
for (const auto& s : ownSynchronizations()) {
if (s->isSyncing()) {
cancelledAnySync = true;
s->cancel();
setState(State::Loaded);
}
}
if (cancelledAnySync) {
setState(State::Loaded);
}
return cancelledAnySync;
}
bool Asset::restartAllSynchronizations() {
cancelAllSynchronizations();
return startSynchronizations();
}
float Asset::requiredSynchronizationProgress() {
std::vector<std::shared_ptr<Asset>> assets = requiredSubTreeAssets();
return syncProgress(assets);
}
float Asset::requestedSynchronizationProgress() {
std::vector<std::shared_ptr<Asset>> assets = subTreeAssets();
return syncProgress(assets);
}
void Asset::load() {
LDEBUG("Loading asset " << id());
if (isLoaded()) {
return;
}
bool loaded = loader()->loadAsset(shared_from_this());
setState(loaded ? State::Loaded : State::LoadingFailed);
}
void Asset::unloadIfUnwanted() {
if (hasLoadedParent()) {
return;
}
unload();
}
void Asset::unload() {
LDEBUG("Unloading asset " << id());
if (!isLoaded()) {
return;
}
loader()->unloadAsset(shared_from_this());
setState(State::Unloaded);
for (std::shared_ptr<Asset> child : requiredAssets()) {
unrequire(child);
}
for (std::shared_ptr<Asset> child : requestedAssets()) {
unrequest(child);
}
}
void Asset::initialize() {
if (isInitialized()) {
return;
}
if (!isSynchronized()) {
LERROR("Cannot initialize unsynchronized asset " << id());
return;
}
LDEBUG("Initializing asset " << id());
// 1. Initialize required children
for (auto& child : _requiredAssets) {
child->initialize();
}
// 2. Call onInitialize in Lua
try {
loader()->callOnInitialize(this);
} catch (const ghoul::lua::LuaRuntimeException& e) {
LERROR("Failed to initialize asset " << id() << ". " <<
e.component << ": " << e.message);
// TODO: rollback;
return;
}
// 3. Update the internal state
setState(Asset::State::Initialized);
// 4. Call dependency initialization functions
// Now that both this and all children are initialized.
for (auto& child : _requiredAssets) {
try {
loader()->callOnDependencyInitialize(child.get(), this);
} catch (const ghoul::lua::LuaRuntimeException& e) {
LERROR("Failed to initialize required asset " <<
child->id() << " of " << id() << ". " <<
e.component << ": " << e.message);
// TODO: rollback;
}
}
// 5. Call dependency initialization function of the child and this
// if the requested child was initialized before this.
for (auto& child : _requestedAssets) {
if (child->isInitialized()) {
try {
loader()->callOnDependencyInitialize(child.get(), this);
} catch (const ghoul::lua::LuaRuntimeException& e) {
LERROR("Failed to initialize requested asset " <<
child->id() << " of " << id() << ". " <<
e.component << ": " << e.message);
// TODO: rollback;
}
}
}
// 6. Ask requested children to initialize if they are not already initialized.
for (auto& child : _requestedAssets) {
if (child->isSynchronized()) {
child->initialize();
}
}
// 7. Call dependency initialization function of this and the parent
// if the requesting parent was initialized before this.
for (auto& parent : _requestingAssets) {
std::shared_ptr<Asset> p = parent.lock();
if (p && p->state() == State::Initialized) {
try {
loader()->callOnDependencyInitialize(this, p.get());
} catch (const ghoul::lua::LuaRuntimeException& e) {
LERROR("Failed to initialize required asset " <<
id() << " of " << p->id() << ". " <<
e.component << ": " << e.message);
// TODO: rollback;
}
}
}
}
void Asset::deinitializeIfUnwanted() {
if (hasInitializedParent()) {
return;
}
deinitialize();
}
void Asset::deinitialize() {
if (state() != Asset::State::Initialized) {
return;
}
LDEBUG("Denitializing asset " << id());
// Notify children
for (auto& dependency : _requiredAssets) {
try {
loader()->callOnDependencyDeinitialize(dependency.get(), this);
}
catch (const ghoul::lua::LuaRuntimeException& e) {
LERROR("Failed to deinitialize requested asset " <<
dependency->id() << " of " << id() << ". " <<
e.component << ": " << e.message);
// TODO: rollback?
}
}
for (auto& dependency : _requestedAssets) {
if (dependency->state() == State::Initialized) {
try {
loader()->callOnDependencyDeinitialize(dependency.get(), this);
}
catch (const ghoul::lua::LuaRuntimeException& e) {
LERROR("Failed to deinitialize requested asset " <<
dependency->id() << " of " << id() << ". " <<
e.component << ": " << e.message);
// TODO: rollback?
}
}
}
try {
loader()->callOnDeinitialize(this);
}
catch (const ghoul::lua::LuaRuntimeException& e) {
LERROR("Failed to deinitialize asset " << id() << ". " <<
e.component << ": " << e.message);
// TODO: rollback?
return;
}
// Call onDeinitialize in Lua
setState(Asset::State::Loaded);
// Make sure no dependencies are left dangling
for (auto& dependency : childAssets()) {
dependency->deinitializeIfUnwanted();
}
}
std::string Asset::id() const {
return _hasAssetPath ? _assetPath : "$root";
}
std::string Asset::assetFilePath() const {
return _assetPath;
}
bool Asset::hasAssetFile() const {
return _hasAssetPath;
}
std::string Asset::assetDirectory() const {
return ghoul::filesystem::File(_assetPath).directoryName();
}
std::string Asset::assetName() const {
return _assetName;
}
AssetLoader* Asset::loader() const {
return _loader;
}
bool Asset::requires(const Asset* asset) const {
const auto it = std::find_if(
_requiredAssets.begin(),
_requiredAssets.end(),
[asset](std::shared_ptr<Asset> dep) {
return dep.get() == asset;
}
);
return it != _requiredAssets.end();
}
void Asset::require(std::shared_ptr<Asset> child) {
if (state() != Asset::State::Unloaded) {
throw ghoul::RuntimeError("Cannot require child asset when already loaded");
}
auto it = std::find(_requiredAssets.begin(),
_requiredAssets.end(),
child);
if (it != _requiredAssets.end()) {
// Do nothing if the requirement already exists.
return;
}
_requiredAssets.push_back(child);
child->_requiringAssets.push_back(shared_from_this());
if (!child->isLoaded()) {
child->load();
}
if (isSynchronized() && child->isLoaded() && !child->isSynchronized()) {
child->startSynchronizations();
}
if (isInitialized() && child->isSynchronized() && !child->isInitialized()) {
child->initialize();
}
}
void Asset::unrequire(std::shared_ptr<Asset> child) {
if (state() != Asset::State::Unloaded) {
throw ghoul::RuntimeError("Cannot unrequire child asset is in a loaded state");
}
auto childIt = std::find(
_requiredAssets.begin(),
_requiredAssets.end(),
child);
if (childIt == _requiredAssets.end()) {
// Do nothing if the request node not exist.
return;
}
_requiredAssets.erase(childIt);
auto parentIt = std::find_if(
child->_requiringAssets.begin(),
child->_requiringAssets.end(),
[this](std::weak_ptr<Asset> a) {
return a.lock().get() == this;
}
);
if (parentIt == child->_requiringAssets.end()) {
return;
}
child->_requiringAssets.erase(parentIt);
child->deinitializeIfUnwanted();
child->cancelUnwantedSynchronizations();
child->unloadIfUnwanted();
}
void Asset::request(std::shared_ptr<Asset> child) {
auto it = std::find(_requestedAssets.begin(),
_requestedAssets.end(),
child);
if (it != _requestedAssets.end()) {
// Do nothing if the request already exists.
return;
}
_requestedAssets.push_back(child);
child->_requestingAssets.push_back(shared_from_this());
if (!child->isLoaded()) {
child->load();
}
if (!child->isLoaded()) {
unrequest(child);
}
if (isSynchronized()) {
if (child->isLoaded() && !child->isSynchronized()) {
child->startSynchronizations();
}
}
if (isInitialized()) {
if (child->isSynchronized() && !child->isInitialized()) {
child->initialize();
}
if (!child->isInitialized()) {
unrequest(child);
}
}
}
void Asset::unrequest(std::shared_ptr<Asset> child) {
auto childIt = std::find(
_requestedAssets.begin(),
_requestedAssets.end(),
child);
if (childIt == _requestedAssets.end()) {
// Do nothing if the request node not exist.
return;
}
_requestedAssets.erase(childIt);
auto parentIt = std::find_if(
child->_requestingAssets.begin(),
child->_requestingAssets.end(),
[this](std::weak_ptr<Asset> a) {
return a.lock().get() == this;
}
);
if (parentIt == child->_requestingAssets.end()) {
return;
}
child->_requestingAssets.erase(parentIt);
child->deinitializeIfUnwanted();
child->cancelUnwantedSynchronizations();
child->unloadIfUnwanted();
}
bool Asset::requests(const Asset* asset) const {
const auto it = std::find_if(
_requestedAssets.begin(),
_requestedAssets.end(),
[asset](std::shared_ptr<Asset> dep) {
return dep.get() == asset;
}
);
return it != _requiredAssets.end();
}
std::vector<std::shared_ptr<Asset>> Asset::requiredAssets() const {
return _requiredAssets;
}
std::vector<std::shared_ptr<Asset>> Asset::requiringAssets() const {
std::vector<std::shared_ptr<Asset>> assets;
assets.reserve(_requiringAssets.size());
for (auto& a : _requiringAssets) {
std::shared_ptr<Asset> shared = a.lock();
if (shared) {
assets.push_back(shared);
}
}
return assets;
}
std::vector<std::shared_ptr<Asset>> Asset::requestedAssets() const {
return _requestedAssets;
}
std::vector<std::shared_ptr<Asset>> Asset::requestingAssets() const {
std::vector<std::shared_ptr<Asset>> assets;
assets.reserve(_requestingAssets.size());
for (auto& a : _requestingAssets) {
std::shared_ptr<Asset> shared = a.lock();
if (shared) {
assets.push_back(shared);
}
}
return assets;
}
std::vector<std::shared_ptr<Asset>> Asset::childAssets() const {
std::vector<std::shared_ptr<Asset>> children;
children.reserve(_requiredAssets.size() + _requestedAssets.size());
children.insert(children.end(), _requiredAssets.begin(), _requiredAssets.end());
children.insert(children.end(), _requestedAssets.begin(), _requestedAssets.end());
return children;
}
std::vector<std::shared_ptr<Asset>> Asset::parentAssets() const {
std::vector<std::shared_ptr<Asset>> parents;
std::vector<std::shared_ptr<Asset>> requiring = requiringAssets();
std::vector<std::shared_ptr<Asset>> requesting = requestingAssets();
parents.reserve(requiring.size() + requesting.size());
parents.insert(parents.end(), requiring.begin(), requiring.end());
parents.insert(parents.end(), requesting.begin(), requesting.end());
return parents;
}
bool Asset::isRequired() const {
return !_requiringAssets.empty();
}
bool Asset::isRequested() const {
return !_requestingAssets.empty();
}
bool Asset::shouldBeInitialized() const {
std::vector<std::shared_ptr<Asset>> parents = parentAssets();
const auto initializedAsset = std::find_if(
parents.begin(),
parents.end(),
[](const std::shared_ptr<Asset>& a) {
return a->state() == State::Initialized;
}
);
return initializedAsset != parents.end();
}
}