/** * @RDE_HEADER@ * InfrastructureBuilder * $Id$ * $HeadURL$ * $Author$ * Created by Christian Fröbel, SLSE, 2017-11-29 */ #include "asset_assignment_manager.h" #include "exercise.h" #include "asset.h" #include "mounting_point.h" #include #include #include #include #include namespace { bool isAssigned(const InfrastructureManagement::InstrumentationAsset &asset) { return asset.availableInExercise().size() > 0; } int assignedExercise(const InfrastructureManagement::InstrumentationAsset &asset) { Q_ASSERT(asset.availableInExercise().size() == 1); return asset.availableInExercise()[0]; } bool isMountingPoint(const InfrastructureManagement::StructureElement &structureElement) { return structureElement.structureElementTypeId() == InfrastructureManagement::StructureElementType::MountingPoint; } } // anonymous namespace namespace AudioVideoManagement { AssetAssignmentManager *AssetAssignmentManager::create(InfrastructureManagement::Interface::Dtc *ismDtc) { Q_ASSERT(ismDtc); AssetAssignmentManager *newAssetAssignmentManager = new AssetAssignmentManager; connect(ismDtc, &InfrastructureManagement::Interface::Dtc::instrumentationAssetChanged, newAssetAssignmentManager, &AssetAssignmentManager::onAssetChanged); connect(ismDtc, &InfrastructureManagement::Interface::Dtc::mainStructureElementChanged, newAssetAssignmentManager, &AssetAssignmentManager::onMainStructureElementChanged); connect(ismDtc, &InfrastructureManagement::Interface::Dtc::mountingPointAssetChanged, newAssetAssignmentManager, &AssetAssignmentManager::onMountingChanged); // No need to query the DTC here as it is not yet activated. // But explicitly check that the DTC (i.e. the contained DataReader) is not enabled yet. Q_ASSERT(!ismDtc->isInstrumentationAssetEnabled()); Q_ASSERT(!ismDtc->isMainStructureElementEnabled()); Q_ASSERT(!ismDtc->isMountingPointAssetEnabled()); return newAssetAssignmentManager; } AssetAssignmentManager::AssetAssignmentManager() : m_logger(getLogger("AssetAssignmentManager")) { // Create a "pool" exercise to hold all elements that are not assigned to any exercise. std::unique_ptr poolExercise(new Exercise(ExerciseContext::defaultContext())); m_exercises[ExerciseContext::defaultContext()] = std::move(poolExercise); } AssetAssignmentManager::~AssetAssignmentManager() = default; void AssetAssignmentManager::onAssetChanged(const InfrastructureManagement::InstrumentationAsset &asset, DataModification modType) { switch (modType) { case DataModification::eAdded: return addAsset(asset); case DataModification::eModified: return modifyAsset(asset); case DataModification::eRemoved: return removeAsset(asset); } Q_ASSERT(false); } void AssetAssignmentManager::onMainStructureElementChanged(const InfrastructureManagement::MainStructureElement &data, DataModification modType) { // Is the removal of MainStructureElements a valid UseCase or does that only happen when stopping the services? // For now, we simply ignore the removal. if (modType == DataModification::eRemoved) return; if (modType == DataModification::eAdded) { for (const auto &structureElement: data.elements()) { if (!isMountingPoint(structureElement)) continue; std::unique_ptr newMountingPoint(new MountingPoint(structureElement)); pool()->add(std::move(newMountingPoint)); } processPendingMounts(); return; } Q_ASSERT(modType == DataModification::eModified); // What to do on modification? // What does a modification mean? // Are the contained StructureElements still the same as before? // For now we simply assume that the MountingPoints are still the same // and we just update them. for (const auto &structureElement: data.elements()) { if (!isMountingPoint(structureElement)) continue; MountingPoint *mountingPoint = findMountingPoint(structureElement.id()).second; Q_ASSERT(mountingPoint); if (mountingPoint) mountingPoint->update(structureElement); } } void AssetAssignmentManager::onMountingChanged(const InfrastructureManagement::MountingPointAsset &mount, DataModification modType) { switch (modType) { case DataModification::eAdded: return addMount(mount); case DataModification::eRemoved: return removeMount(mount); case DataModification::eModified: Q_ASSERT(false); break; } Q_ASSERT(false); } void AssetAssignmentManager::addAsset(const InfrastructureManagement::InstrumentationAsset &asset) { // Put the new Asset into the pool exercise. std::unique_ptr newAsset(new Asset(asset)); Asset *newAsset_ = newAsset.get(); pool()->add(std::move(newAsset)); processPendingMounts(); if (isAssigned(asset)) { Exercise *targetExercise = exercise(assignedExercise(asset)); tryAssignAsset(newAsset_, pool(), targetExercise, boost::none); } } /** * The modification of an Asset possibly only affects the assignment. */ void AssetAssignmentManager::modifyAsset(const InfrastructureManagement::InstrumentationAsset &asset) { if (hasPendingModifications(asset.id())) return m_pendingModifications.push_back(asset); auto found = findAsset(asset.id()); Asset *foundAsset = found.second; Q_ASSERT(foundAsset); Exercise *currentExercise = found.first; Exercise *targetExercise = isAssigned(asset) ? exercise(assignedExercise(asset)) : pool(); if (targetExercise == currentExercise) { foundAsset->update(asset); return; // no change in assignment } if (targetExercise == pool()) { // unassign unassignAsset(foundAsset, currentExercise, asset); processPendingUnmounts(); return; } // assign or reassign tryAssignAsset(foundAsset, currentExercise, targetExercise, asset); } /** * If the Asset is assigned, it will be unassigned. * If it is not unmounted yet, defer the removal. * The pending removals will be processed after the Unmount. */ void AssetAssignmentManager::removeAsset(const InfrastructureManagement::InstrumentationAsset &asset) { auto assetAndExercise = findAsset(asset.id()); Asset *foundAsset = assetAndExercise.second; Exercise *exercise = assetAndExercise.first; Q_ASSERT(foundAsset); if (exercise != pool()) { unassignAsset(foundAsset, exercise, boost::none); processPendingUnmounts(); } if (foundAsset->isMounted()) { m_pendingRemoves.push_back(asset); return; } pool()->take(foundAsset); // implicit delete } /** * Try to assign the Asset to an Exercise. * Assets can only be assigned if they are mounted. * If the Asset is not mounted yet (because the info was not yet published), * the assignment will be deferred. It will be processed after the mount. * The Asset and MountingPoint must still be in the pool Exercise. * @param modifiedData only set when called as a result of a modification */ void AssetAssignmentManager::tryAssignAsset(Asset *asset, Exercise *currentExercise, Exercise *targetExercise, const boost::optional &modifiedData) { Q_ASSERT(asset); Q_ASSERT(currentExercise); Q_ASSERT(targetExercise); Q_ASSERT(isAssigned(modifiedData.get_value_or(asset->data()))); if (!asset->isMounted()) { m_pendingModifications.push_back(modifiedData.get_value_or(asset->data())); return; } currentExercise->moveMountedTo(asset, targetExercise, modifiedData); } /** * @param modifiedData only set when called as a result of a modification */ void AssetAssignmentManager::unassignAsset(Asset *asset, Exercise *exercise, const boost::optional &modifiedData) { Q_ASSERT(asset); Q_ASSERT(exercise != pool()); // Q_ASSERT(!isAssigned(asset->data())); // Assertion does not hold in case the unassignment is a result of a removal. exercise->moveMountedTo(asset, pool(), modifiedData); } /** * Try to mount the Asset. If the mounting was published before the referenced * Asset or MountingPoint, the mounting will be deferred. */ void AssetAssignmentManager::addMount(const InfrastructureManagement::MountingPointAsset &mount) { Asset *asset = pool()->findAsset(mount.assetId()); if (!asset) { m_pendingMounts.push_back(mount); return; } MountingPoint *mountingPoint = pool()->findMountingPoint(mount.structureElementId()); if (!mountingPoint) { m_pendingMounts.push_back(mount); return; } asset->mount(mountingPoint); processPendingModifications(); } void AssetAssignmentManager::removeMount(const InfrastructureManagement::MountingPointAsset &mount) { auto exerciseAndAsset = findAsset(mount.assetId()); Asset *asset = exerciseAndAsset.second; Exercise *exercise = exerciseAndAsset.first; Q_ASSERT(asset); if (!asset) return; tryUnmount(asset, exercise); processPendingRemoves(); } /** * Try to unmount the Asset. * Unmonting can only happen in the pool Exercise. * If the Asset is not yet in the pool Exercise (because the updated Asset * was not yet published), the unmount will be deferred. * It will be processed after the Asset was unassigned. */ void AssetAssignmentManager::tryUnmount(Asset *asset, Exercise *exercise) { Q_ASSERT(asset); bool inPoolExercise = false; if (exercise) inPoolExercise = exercise == pool(); else inPoolExercise = pool()->findAsset(asset->data().id()); if (!inPoolExercise) { m_pendingUnmounts.push_back(asset); return; } asset->unmount(); } template void AssetAssignmentManager::processPending(Container &pendingSource, Function f) { if (pendingSource.empty()) return; logPending(); Container pending; std::swap(pending, pendingSource); for (const auto &element: pending) f(element); logPending(); } /** * We want to make sure that assignments are always processed after the corresponding mount. * On DDS these are not always published in that order. * Thus we need to internally defer assignments that are published before the mount. */ void AssetAssignmentManager::processPendingModifications() { using namespace std::placeholders; processPending(m_pendingModifications, std::bind(&AssetAssignmentManager::modifyAsset, this, _1)); } /** * Same as for processPendingAssigns() but this time we internally defer * unmounts that are published before the unassign. */ void AssetAssignmentManager::processPendingUnmounts() { using namespace std::placeholders; processPending(m_pendingUnmounts, std::bind(&AssetAssignmentManager::tryUnmount, this, _1, nullptr)); } void AssetAssignmentManager::processPendingMounts() { using namespace std::placeholders; processPending(m_pendingMounts, std::bind(&AssetAssignmentManager::addMount, this, _1)); } void AssetAssignmentManager::processPendingRemoves() { using namespace std::placeholders; processPending(m_pendingRemoves, std::bind(&AssetAssignmentManager::removeAsset, this, _1)); } bool AssetAssignmentManager::hasPendingModifications(int assetId) { auto idMatches = [assetId] (const InfrastructureManagement::InstrumentationAsset &asset) { return asset.id() == assetId; }; auto found = std::find_if(m_pendingModifications.begin(), m_pendingModifications.end(), idMatches); return found != m_pendingModifications.end(); } void AssetAssignmentManager::logPending() { DEBUG_LOG(m_logger) << "pending Mounts, Modifications (Asset), Unmounts, Removes: " << m_pendingMounts.size() << ", " << m_pendingModifications.size() << ", " << m_pendingUnmounts.size() << ", " << m_pendingRemoves.size(); } bool AssetAssignmentManager::hasPending() const { return !(m_pendingMounts.empty() && m_pendingModifications.empty() && m_pendingUnmounts.empty() && m_pendingRemoves.empty()); } Exercise *AssetAssignmentManager::pool() const { auto it = m_exercises.find(ExerciseContext::defaultContext()); return (it == m_exercises.end() ? nullptr : it->second.get()); } Exercise *AssetAssignmentManager::exercise(int id) { ExerciseContext context(id); auto found = m_exercises.find(context); if (found != m_exercises.end()) return found->second.get(); std::unique_ptr newExercise(new Exercise(context)); Exercise *newExercise_ = newExercise.get(); m_exercises[context] = std::move(newExercise); return newExercise_; } void AssetAssignmentManager::removeExercise(int id) { ExerciseContext context(id); auto found = m_exercises.find(context); Q_ASSERT(found != m_exercises.end()); if (found != m_exercises.end()) m_exercises.erase(found); // implicit delete } std::pair AssetAssignmentManager::findAsset(int id) { for (const auto &exercise: m_exercises) { Asset *asset = exercise.second->findAsset(id); if (asset) return {exercise.second.get(), asset}; } return {nullptr, nullptr}; } std::pair AssetAssignmentManager::findMountingPoint(int id) { for (const auto &exercise: m_exercises) { MountingPoint *mountingPoint = exercise.second->findMountingPoint(id); if (mountingPoint) return {exercise.second.get(), mountingPoint}; } return {nullptr, nullptr}; } } // namespace AudioVideoManagement