/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "OdaCommon.h" #include "DbImpAssocNetwork.h" #include "DbAssocNetwork.h" #include "DbFiler.h" #include "DbDictionary.h" #include "DbImpAssocAction.h" #include "DbAssocAction.h" #include "DbAssoc2dConstraintGroup.h" #include "DbAssocNetCloneCtx.h" #include "DbAssocVariable.h" #include "DbHostAppServices.h" #include "DbAudit.h" #include "DbAssocManager.h" #include "DebugStuff.h" #include "SaveState.h" #include "DbNamedObjectIndex.h" OdDbImpAssocNetwork::OdDbImpAssocNetwork() : OdDbImpAssocAction() , m_maxChildActionIdx(0) , m_fixStatusConsistency(false) { m_isBase = false; } const std::map& OdDbImpAssocNetwork::getActions() const { return m_actions; } const std::set& OdDbImpAssocNetwork::getActionsToEvaluate() const { return m_actionsToEvaluate; } OdResult OdDbImpAssocNetwork::addAction(OdDbAssocNetwork* thisNetwork, const OdDbObjectId& actionId, bool alsoSetAsDatabaseOwner) { OdDbObjectId networkId = thisNetwork->objectId(); OdDbObjectPtr pObj = actionId.openObject(OdDb::kForWrite); if ( pObj.isNull() || !pObj->isKindOf(OdDbAssocAction::desc()) ) return eInvalidInput; OdDbAssocActionPtr pAction = OdDbAssocAction::cast(pObj); if ( networkId== pAction->owningNetwork() && (!alsoSetAsDatabaseOwner || networkId == pAction->ownerId()) && m_actions.find(actionId)!=m_actions.end()) { ODA_FAIL_ONCE(); // temp test marker // #11465 fix audit problem via cloning (select + drag) return eInvalidInput; // already added } pAction->setOwningNetwork(networkId, alsoSetAsDatabaseOwner); if(alsoSetAsDatabaseOwner) pAction->setOwnerId(networkId); m_actions[actionId] = alsoSetAsDatabaseOwner; if( isEvaluationRequest(pAction->status()) ) { m_actionsToEvaluate.insert(actionId); thisNetwork->setStatus(kChangedDirectlyAssocStatus); } if(OdDbNamedObjectIndex* index = OdDbNamedObjectIndex::fromObject(thisNetwork, OdDbAssocVariable::desc())) OdDbNamedObjectIndexNode::fromObject(pAction, index); m_maxChildActionIdx++; OdDbImpAssocAction *pImpAction = OdDbImpAssocAction::getImpObject(pAction); pImpAction->setActionIndex(m_maxChildActionIdx); return eOk; } OdResult OdDbImpAssocNetwork::removeAction(OdDbAssocNetwork* thisNetwork, const OdDbObjectId& actionId, bool alsoEraseIt) { OdDbAssocActionPtr pAction = OdDbAssocAction::cast(actionId.openObject(OdDb::kForWrite)); if (pAction.isNull()) alsoEraseIt = false; else pAction->setOwningNetwork(OdDbObjectId::kNull, false); m_actions.erase(actionId); m_actionsToEvaluate.erase(actionId); thisNetwork->setStatus(kChangedDirectlyAssocStatus); if(OdDbNamedObjectIndexNode* indexNode = OdDbNamedObjectIndexNode::fromObject(pAction)) indexNode->removeFromIndex(); if ( alsoEraseIt ) { pAction->erase(); pAction->downgradeOpen(); } return eOk; } OdResult OdDbImpAssocNetwork::ownedActionStatusChanged(OdDbObject* pThisNetwork, OdDbAssocAction* pOwnedAction, OdDbAssocStatus previousStatus) { OdDbAssocStatus newStatus = pOwnedAction->status(); ODA_ASSERT(newStatus != previousStatus); bool new_toEvaluate = isEvaluationRequest(newStatus) || newStatus == kErasedAssocStatus; bool old_toEvaluate = isEvaluationRequest(previousStatus) || previousStatus == kErasedAssocStatus; if (m_status == kErasedAssocStatus) { return eOk; } else if (old_toEvaluate && !new_toEvaluate) { pThisNetwork->assertWriteEnabled(); ODA_ASSERT(m_actionsToEvaluate.find(pOwnedAction->objectId())!=m_actionsToEvaluate.end()); m_actionsToEvaluate.erase(pOwnedAction->objectId()); if(m_actionsToEvaluate.size()==0) setStatus(pThisNetwork, kIsUpToDateAssocStatus, true, false); } else if (!old_toEvaluate && new_toEvaluate) { pThisNetwork->assertWriteEnabled(); ODA_ASSERT(m_actionsToEvaluate.find(pOwnedAction->objectId())==m_actionsToEvaluate.end()); m_actionsToEvaluate.insert(pOwnedAction->objectId()); setStatus(pThisNetwork, kChangedDirectlyAssocStatus, true, false); } return eOk; } void odaaEnableStatusConsistencyCheck(OdDbAssocNetwork* network, bool doIt) { static_cast(OdDbImpAssocAction::getImpObject(network))->m_fixStatusConsistency = doIt; } OdResult OdDbImpAssocNetwork::dwgInFields(OdDbAssocAction *pSelf, OdDbDwgFiler* pFiler) { OdResult res = OdDbImpAssocAction::dwgInFields(pSelf, pFiler); if ( res != eOk) return res; #ifdef _DEBUG int val = pFiler->rdInt16(); ODA_ASSERT(val == 0); // Version? #else pFiler->rdInt16(); #endif // _DEBUG m_maxChildActionIdx = pFiler->rdInt32(); OdUInt32 nRefs = pFiler->rdInt32(); m_actions.clear(); OdDbObjectId id; bool fileFiler = pFiler->filerType()==OdDbFiler::kFileFiler; while (nRefs--) { bool owned = pFiler->rdBool(); if (owned) id = pFiler->rdHardOwnershipId(); else id = pFiler->rdSoftPointerId(); if(!fileFiler || !id.isNull()) { m_actions[id]=owned; } } if(OdDbNamedObjectIndex* index = OdDbNamedObjectIndex::fromObject(pSelf, OdDbAssocVariable::desc())) index->setNeedRegen(); // Some more references nRefs = pFiler->rdInt32(); m_actionsToEvaluate.clear(); while (nRefs--) { id = pFiler->rdSoftPointerId(); if(!fileFiler || !id.isNull()) m_actionsToEvaluate.insert(id); } if (pFiler->filerType() == OdDbFiler::kFileFiler && !m_actions.empty()) ::odaaEnableStatusConsistencyCheck(static_cast(pSelf)); return eOk; } void OdDbImpAssocNetwork::dwgOutFields(OdDbDwgFiler* pFiler, OdDbObjectId objectId) const { OdDbImpAssocAction::dwgOutFields(pFiler, objectId); pFiler->wrInt16(0); // Version? pFiler->wrInt32(m_maxChildActionIdx); pFiler->wrInt32(OdUInt32(m_actions.size())); for (const auto& item : m_actions) { pFiler->wrBool(item.second); if (item.second) { pFiler->wrHardOwnershipId(item.first); } else { pFiler->wrSoftPointerId(item.first); } } //Some more references pFiler->wrInt32(OdUInt32(m_actionsToEvaluate.size())); for (OdDbObjectId id : m_actionsToEvaluate) { pFiler->wrSoftPointerId(id); } } OdResult OdDbImpAssocNetwork::dxfInFields(OdDbDxfFiler* pFiler, OdDbObjectId objectId) { OdResult res = OdDbImpAssocAction::dxfInFields(pFiler, objectId); if (res != eOk) return res; // Check that we are at the correct subclass data if( !pFiler->atSubclassData( OdDbAssocNetwork::desc()->name() )) { return eBadDxfSequence; } NEXT_CODE(90) #ifdef ODA_DIAGNOSTICS int val = #endif // ODA_DIAGNOSTICS pFiler->rdInt32(); ODA_ASSERT(val == 0); // Version? NEXT_CODE(90) m_maxChildActionIdx = pFiler->rdInt32(); NEXT_CODE(90) OdUInt32 nRefs = pFiler->rdInt32(); m_actions.clear(); OdDbObjectId id; bool fileFiler = pFiler->filerType() == OdDbFiler::kFileFiler; bool owned; while (nRefs--) { switch(pFiler->nextItem()) { case 330: owned = false; break; case 360: owned = true; break; default: ODA_FAIL(); throw OdError(eBadDxfSequence); } id = pFiler->rdObjectId(); if (!fileFiler || !id.isNull()) { m_actions[id]=owned; } } if(OdDbNamedObjectIndex* index = OdDbNamedObjectIndex::fromObject(objectId.openObject(OdDb::kForRead, true), OdDbAssocVariable::desc())) index->setNeedRegen(); // More references NEXT_CODE(90) nRefs = pFiler->rdInt32(); m_actionsToEvaluate.clear(); while (nRefs--) { NEXT_CODE(330) id = pFiler->rdObjectId(); m_actionsToEvaluate.insert(id); } if (pFiler->filerType() == OdDbFiler::kFileFiler && !m_actions.empty()) ::odaaEnableStatusConsistencyCheck(OdDbAssocNetworkPtr(objectId.safeOpenObject())); return eOk; } void OdDbImpAssocNetwork::dxfOutFields(OdDbDxfFiler* pFiler, OdDbObjectId objectId) const { OdDbImpAssocAction::dxfOutFields(pFiler, objectId); pFiler->wrSubclassMarker( OdDbAssocNetwork::desc()->name() ); pFiler->wrInt32(90, 0); // Version ? pFiler->wrInt32(90, m_maxChildActionIdx); pFiler->wrInt32(90, OdUInt32(m_actions.size())); for (const auto& item : m_actions) { if (item.second) { pFiler->wrObjectId(360, item.first); } else { pFiler->wrObjectId(330, item.first); } } pFiler->wrInt32(90, OdUInt32(m_actionsToEvaluate.size())); for (OdDbObjectId id : m_actionsToEvaluate) { pFiler->wrObjectId(330, id); } } void OdDbImpAssocNetwork::composeForLoad(OdDbObject* pObj, OdDb::SaveType format, OdDb::DwgVersion version, OdDbAuditInfo* pAuditInfo) { OdDbImpAssocAction::composeForLoad(pObj, format, version, pAuditInfo); OdDbObjectId id = pObj->objectId(); if ( format == OdDb::kDwg && OdDb::kSTMode == pObj->database()->multiThreadedMode() ) return; for (const auto& item : m_actions) { OdDbObjectPtr pChild = item.first.openObject(); if (pChild.isNull()) continue; OdDbObjectId idOwner = pChild->ownerId(); ODA_ASSERT_VAR(OdDbObjectId idChild = pChild->objectId();) if (idOwner != id) continue; pChild->composeForLoad(format, version, pAuditInfo); } } inline bool OdDbAssocManager_isPostponingEvaluationRequests(OdDbDatabase* db); OdResult OdDbImpAssocNetwork::setStatus(OdDbObject* pThisNetwork, OdDbAssocStatus newStatus, bool notifyOwningNetwork, bool setInOwnedActions) { OdDbImpAssocAction::setStatus(pThisNetwork, newStatus, notifyOwningNetwork, setInOwnedActions); if ( setInOwnedActions && !OdDbAssocManager_isPostponingEvaluationRequests(pThisNetwork->database()) ) { for (const auto& item : m_actions) { OdDbAssocActionPtr curAction = OdDbAssocAction::cast(item.first.openObject(OdDb::kForWrite)); if (!curAction.isNull()) curAction->setStatus(newStatus, false, setInOwnedActions); } } return eOk; } class AssocNetEvalGraphNode : public OdRxObject { OdUInt32 m_flags; OdDbObjectId m_id; OdDbAssocStatus m_saved; mutable OdDbAssocEvaluationPriority m_evalPriority; public: # ifndef NDEBUG OdUInt64 dbgHandle = 0; # endif enum { kIsNew = 1, kIsAction = 2, kIsProcessed = 4, }; AssocNetEvalGraphNode() : m_flags(kIsNew), m_saved(kErasedAssocStatus), m_evalPriority(kCannotDermineAssocEvaluationPriority){} ~AssocNetEvalGraphNode() { } OdDbObjectId objectId() const { return m_id; } void setObjectId(OdDbObjectId id) { m_id = id; } bool isNew() const { return GETBIT(m_flags, kIsNew); } bool isAction() const { return GETBIT(m_flags, kIsAction); } void setIsNew(bool bNew) { SETBIT(m_flags, kIsNew, bNew); } void setIsAction(bool bAction) { SETBIT(m_flags, kIsAction, bAction); } bool isProcessed() const { return GETBIT(m_flags, kIsProcessed); } void setIsProcessed() { SETBIT(m_flags, kIsProcessed, true); } OdDbAssocEvaluationPriority priority() const { if (m_evalPriority == kCannotDermineAssocEvaluationPriority) { OdDbAssocActionPtr action = m_id.safeOpenObject(OdDb::kForWrite); m_evalPriority = action->evaluationPriority(); } return m_evalPriority; } void suppressAction() { OdDbAssocActionPtr action = m_id.safeOpenObject(OdDb::kForWrite); m_saved = action->status(); action->setStatus(kSuppressedAssocStatus, false); } void unsuppressAction() { OdDbAssocActionPtr action = m_id.safeOpenObject(OdDb::kForWrite); action->setStatus(kIsUpToDateAssocStatus, false); action->setStatus(m_saved, false); } void resetPriority() { m_evalPriority = kCannotDermineAssocEvaluationPriority; } }; void clearIfErased(OdDbAssocAction* action) { OdDbAssocNetworkPtr net = OdDbAssocNetwork::cast(action->owningNetwork().openObject(OdDb::kForWrite)); if (net.get()) net->removeAction(action->objectId(), true); else action->erase(true); } #ifndef NDEBUG void DBG_DUMP_OBJID(const OdDbObjectId& id) { OdDbObjectPtr object = id.openObject(OdDb::kForRead, true); OdDbAssocActionPtr action = OdDbAssocAction::cast(object); OdString className; if (action.get()) { OdDbObjectPtr body = action->actionBody().openObject(OdDb::kForRead, true); if (body.get()) className.format(L"%ls:[%ls]", object->isA()->name().c_str(), body->isA()->name().c_str()); } if (object.get() && className.isEmpty()) className = object->isA()->name(); ODA_TRACE3("%" PRIX64W L":%ls%ls", (OdUInt64)id.getHandle(), className.c_str(), (id.isErased() ? L" (erased)" : L"")); } void DBG_DUMP_STATUS(OdDbAssocStatus status) { switch(status) { case kIsUpToDateAssocStatus : ODA_TRACE0(" UpToDate"); break; case kChangedDirectlyAssocStatus : ODA_TRACE0(" Directly"); break; case kChangedTransitivelyAssocStatus : ODA_TRACE0(" Transitively"); break; case kChangedNoDifferenceAssocStatus : ODA_TRACE0(" NoDifference"); break; case kFailedToEvaluateAssocStatus : ODA_TRACE0(" Failed"); break; case kErasedAssocStatus : ODA_TRACE0(" Erased"); break; case kSuppressedAssocStatus : ODA_TRACE0(" Suppressed"); break; case kUnresolvedAssocStatus : ODA_TRACE0(" Unresolved"); break; } } void DBG_DUMP_NODE(const AssocNetEvalGraphNode* node) { OdDbAssocEvaluationPriority priority = node->priority(); ODA_TRACE1("%+10d : ", priority); DBG_DUMP_OBJID(node->objectId()); OdDbAssocActionPtr action = node->objectId().safeOpenObject(); DBG_DUMP_STATUS(action->status()); ODA_TRACE0("\n"); } #include "DbDatabaseReactor.h" class AssocDbgDatabaseReactor : public OdDbDatabaseReactor { public: void objectAppended(const OdDbDatabase* /*pDb*/, const OdDbObject* pDbObj) { OdUInt64 h = pDbObj->getDbHandle(); ODA_TRACE2(" : Appended %3I64X:%ls\n", h, pDbObj->isA()->name().c_str()); } void objectUnAppended(const OdDbDatabase* /*pDb*/, const OdDbObject* pDbObj) { OdUInt64 h = pDbObj->getDbHandle(); ODA_TRACE2(" : UnAppended %3I64X:%ls\n", h, pDbObj->isA()->name().c_str()); } void objectReAppended(const OdDbDatabase* /*pDb*/, const OdDbObject* pDbObj) { OdUInt64 h = pDbObj->getDbHandle(); ODA_TRACE2(" : ReAppended %3I64X:%ls\n", h, pDbObj->isA()->name().c_str()); } void objectOpenedForModify(const OdDbDatabase* /*pDb*/, const OdDbObject* pDbObj) { OdUInt64 h = pDbObj->getDbHandle(); ODA_TRACE2(" : BeginModify %3I64X:%ls\n", h, pDbObj->isA()->name().c_str()); } void objectModified(const OdDbDatabase* /*pDb*/, const OdDbObject* /*pDbObj*/) { //OdUInt64 h = pDbObj->getDbHandle(); //ODA_TRACE2(" : Modified %3I64X:%ls\n", h, pDbObj->isA()->name().c_str()); } void objectErased(const OdDbDatabase* /*pDb*/, const OdDbObject* pDbObj, bool pErased) { OdUInt64 h = pDbObj->getDbHandle(); ODA_TRACE3(" : %ls %3I64X:%ls\n", pErased ? OD_T("Erased ") : OD_T("Unerased"), h, pDbObj->isA()->name().c_str()); } void headerSysVarWillChange(const OdDbDatabase* /*pDb*/, const OdString& name) { ODA_TRACE1(" : Will Change %ls\n", name.c_str()); } void headerSysVarChanged(const OdDbDatabase* /*pDb*/, const OdString& /*name*/) { //ODA_TRACE1(" : Changed %ls\n", name.c_str()); } }; #else #define DBG_DUMP_OBJID(id) #define DBG_DUMP_STATUS(status) #define DBG_DUMP_NODE(node) #endif class AssocNetEvalGraph; typedef AssocNetEvalGraph RequestedToEvaluate; class AssocNetEvalGraph : public OdDbActionsToEvaluateCallback, public OdStaticRxObject { typedef AssocNetEvalGraphNode EvalGrapNode; typedef OdSmartPtr EvalGrapNodePtr; typedef OdHashMap> Id2NodeHash; Id2NodeHash m_ids; typedef OdArray EvalGrapNodePtrArray; EvalGrapNodePtrArray m_actionsToEvaluate; EvalGrapNode* lookupNode(const OdDbObjectId& objectId) { Id2NodeHash::iterator it = m_ids.find(objectId); if(it!= m_ids.end()) return it->second.get(); return nullptr; } EvalGrapNode* newNode(const OdDbObjectId& objectId) { OdSmartPtr node = OdRxObjectImpl::createObject(); m_ids.insert(std::pair(objectId, node)); node->setObjectId(objectId); # ifndef NDEBUG node->dbgHandle = objectId.getHandle(); # endif return node; } EvalGrapNode* mapNode(const OdDbObjectId& objectId) { EvalGrapNode* node = lookupNode(objectId); if (!node) node = newNode(objectId); return node; } OdDbDatabase* m_db; bool m_isEvaluationInProgress; OdDbAssocNetwork* m_topAssocNetwork; EvalGrapNodePtrArray m_notEvaluated; public: AssocNetEvalGraph(OdDbDatabase* db) { m_isEvaluationInProgress = false; addToDb(m_db = db); } ~AssocNetEvalGraph() { removeFromDb(m_db); applyNewStates(); // keeps states consistent in case of exception } virtual void needsToEvaluate(const OdDbObjectId &objectId, OdDbAssocStatus newStatus, bool ownedActionsAlso) { OdDbObjectPtr object = objectId.openObject(OdDb::kForWrite); if (object.get()) { if (object->isKindOf(OdDbAssocAction::desc())) { actionNeedsToEvaluate(static_cast(object.get()), newStatus); if (object->isKindOf(OdDbAssocNetwork::desc()) && ownedActionsAlso) { OdDbObjectIdArray actions = static_cast(object.get())->getActions(); for (OdDbObjectIdArray::const_iterator it = actions.begin(), end = actions.end(); it != end; ++it) needsToEvaluate(*it, newStatus, ownedActionsAlso); } } else if (object->isKindOf(OdDbAssocDependency::desc())) dependencyNeedsToEvaluate(static_cast(object.get()), newStatus); else objectNeedsToEvaluate(object.get(), newStatus); } } virtual void dependencyNeedsToEvaluate(OdDbAssocDependency* dep, OdDbAssocStatus newStatus) { if (!dep) return; (void)newStatus; ODA_TRACE0("dependency : "); DBG_DUMP_OBJID(dep->objectId()); DBG_DUMP_STATUS(newStatus); ODA_TRACE0("\n"); propagateWriteDependencyChange(dep); propagateReadDependencyChange(dep); } void propagateWriteDependencyChange(OdDbAssocDependency* dep) { if (dep->isWriteDependency()) { OdDbObjectId dependentOnObjectId = dep->dependentOnObject(); OdDbObjectPtr obj = dependentOnObjectId.openObject(OdDb::kForWrite); if(obj.get() && mapNode(dependentOnObjectId)->isNew()) objectNeedsToEvaluate(obj, kChangedTransitivelyAssocStatus); } } virtual void actionNeedsToEvaluate(OdDbAssocAction* action, OdDbAssocStatus newStatus) { if (!action) return; if (action->status() == kErasedAssocStatus) { ODA_TRACE0("remove erased action : "); DBG_DUMP_OBJID(action->objectId()); DBG_DUMP_STATUS(newStatus); ODA_TRACE0("\n"); removeErasedAction(action); } else if (isEvaluationRequest(newStatus)) { action->setStatus(newStatus); EvalGrapNode* node = mapNode(action->objectId()); if (node->isNew()) { ODA_TRACE0("action : "); DBG_DUMP_OBJID(action->objectId()); DBG_DUMP_STATUS(newStatus); ODA_TRACE0("\n"); node->setIsAction(true); node->setIsNew(false); action->getDependentActionsToEvaluate(this); m_actionsToEvaluate.append(node); propagateDependenciesOnObject(action); } } } virtual void objectNeedsToEvaluate(const OdDbObject* object, OdDbAssocStatus newStatus) { if (!object) return; if (object->isA()->isDerivedFrom(OdDbAssocAction::desc())) { actionNeedsToEvaluate((OdDbAssocAction*)object, newStatus); } else if (isEvaluationRequest(newStatus)) { EvalGrapNode* node = mapNode(object->objectId()); if (node->isNew()) { ODA_TRACE0("object : "); DBG_DUMP_OBJID(object->objectId()); DBG_DUMP_STATUS(newStatus); ODA_TRACE0("\n"); node->setIsNew(false); propagateDependenciesOnObject(object); } } } void propagateDependenciesOnObject(const OdDbObject* object) { OdDbAssocDependencyPtr dep = OdDbAssocDependency::getFirstDependencyOnObject(object).openObject(OdDb::kForWrite); while (dep.get()) { propagateReadDependencyChange(dep); dep = dep->nextDependencyOnObject().openObject(OdDb::kForWrite); } } void propagateReadDependencyChange(OdDbAssocDependency* dep) { if (dep->isReadDependency()) { OdDbObjectId actionId = dep->owningAction(); OdDbAssocActionPtr action = actionId.openObject(OdDb::kForWrite); if (action.get() && mapNode(actionId)->isNew()) { dep->setStatus(kChangedTransitivelyAssocStatus); actionNeedsToEvaluate(action, kChangedTransitivelyAssocStatus); } } } void evaluateActions(OdDbAssocNetwork* topAssocNetwork) { if (m_actionsToEvaluate.empty()) return; m_topAssocNetwork = topAssocNetwork; ODA_TRACE0("************ Evaluating network of actions ************\n"); m_isEvaluationInProgress = true; ODA_TRACE0("* Sorting actions to evaluate... \n"); sortNodesByPriority(); OdDbAssocEvaluationCallback* pEvaluationCallback = topAssocNetwork->currentEvaluationCallback(); int evaluated = 0; do { if(pEvaluationCallback && pEvaluationCallback->cancelActionEvaluation()) { ODA_TRACE0("* CANCELING evaluation...\n"); break; } EvalGrapNode* node = m_actionsToEvaluate.last(); // highest priority node if (node->priority() > 0 || m_actionsToEvaluate.size()==1) { ODA_TRACE0("* Evaluating...\n"); DBG_DUMP_NODE(node); evaluateActionNode(node); ++evaluated; m_actionsToEvaluate.removeLast(); } else if (evaluated) { evaluated = 0; ODA_TRACE0("* Updating priorities for:\n"); #ifndef NDEBUG for (int dd = m_actionsToEvaluate.size() - 1; dd >= 0; --dd) { DBG_DUMP_NODE(m_actionsToEvaluate[dd]); } #endif ODA_TRACE0("* Resorting actions to evaluate...\n"); resortNodesByPriority(); } else { ODA_TRACE0("* Breaking deadlock...\n"); node = m_actionsToEvaluate.first(); // least priority node OdDbAssocActionPtr action = node->objectId().safeOpenObject(OdDb::kForWrite); OdDbAssocManager::requestToEvaluate(action->objectId(), kChangedDirectlyAssocStatus, false); action->setStatus(kIsUpToDateAssocStatus); m_actionsToEvaluate.removeFirst(); resortNodesByPriority(); } } while (!m_actionsToEvaluate.empty()); m_isEvaluationInProgress = false; } void unsupressNotEvaluatedActions() { if (m_notEvaluated.size()) { ODA_TRACE0("*************** Not evaluated actions *****************\n"); do { m_notEvaluated.last()->unsuppressAction(); DBG_DUMP_NODE(m_notEvaluated.last()); m_notEvaluated.removeLast(); } while (m_notEvaluated.size()); } ODA_TRACE0("******************* Evaluation done *******************\n\n"); } void sortNodesByPriority() { std::sort(m_actionsToEvaluate.begin(), m_actionsToEvaluate.end(), [](const EvalGrapNodePtr& a1, const EvalGrapNodePtr& a2) { return a1->priority() < a2->priority(); }); } static void removeErasedAction(OdDbAssocAction* action) { action->erase(true); } void evaluateActionNode(EvalGrapNode* actionNode) { OdDbObjectId id = actionNode->objectId(); OdDbAssocActionPtr action = id.openObject(OdDb::kForWrite); if (action.get()) { if (action->status() == kErasedAssocStatus) removeErasedAction(action); else if(isEvaluationRequest(action->status())) { #ifndef NDEBUG OdStaticRxObject _assocDbgDatabaseReactor; action->database()->addReactor(&_assocDbgDatabaseReactor); #endif try { action->evaluate(m_topAssocNetwork->currentEvaluationCallback()); } catch(const OdError& #ifndef ODA_NON_TRACING e #endif ) { ODA_TRACE0(" : exception caught: \""); ODA_TRACE(e.description().c_str()); ODA_TRACE0("\"\n"); } #ifndef NDEBUG action->database()->removeReactor(&_assocDbgDatabaseReactor); #endif OdDbAssocStatus status = action->status(); if (status == kErasedAssocStatus) removeErasedAction(action); else if (isEvaluationRequest(status)) { actionNode->suppressAction(); m_notEvaluated.append(actionNode); ODA_TRACE0(" : was not evaluated\n"); } } } } static void reset_priority(EvalGrapNode* actionNode) { actionNode->resetPriority(); } void resortNodesByPriority() { std::for_each(m_actionsToEvaluate.begin(), m_actionsToEvaluate.end(), &reset_priority); sortNodesByPriority(); } void assembly(OdDbAssocNetwork* topAssocNetwork) { ODA_TRACE0("****** Assembling Assoc.Network Evaluation Graph ******\n"); ODA_TRACE(m_db->numActiveTransactions()>0 ? L"* Database transaction is in progress; will flush all changes\n" : L""); topAssocNetwork->getDependentActionsToEvaluate(this); } typedef OdHashMap, OdHashSet_PtrHasher > Id2Request; private: Id2Request buf; typedef OdHashSet > IdHash; IdHash allActionsEvaluated; static void processRequest(const Id2Request::value_type& req) { ODA_TRACE0(" : "); DBG_DUMP_OBJID(req.first); DBG_DUMP_STATUS(req.second.first); ODA_TRACE0("\n"); doRequest(req.first, req.second.first, req.second.second); } static void setStatus(OdDbAssocStatus newStatus, OdDbAssocAction *object, bool ownedActionsAlso) { if (newStatus == kErasedAssocStatus) { object->setStatus(kChangedDirectlyAssocStatus, true, ownedActionsAlso); object->setStatus(newStatus, false, ownedActionsAlso); } else object->setStatus(newStatus); } static void doRequest(const OdDbObjectId &objectId, OdDbAssocStatus newStatus, bool ownedActionsAlso) { OdDbObjectPtr object = objectId.openObject(OdDb::kForWrite, newStatus==kErasedAssocStatus); if (object.get()) { if (object->isKindOf(OdDbAssocAction::desc())) { setStatus(newStatus, static_cast(object.get()), ownedActionsAlso); if (object->isKindOf(OdDbAssocNetwork::desc()) && ownedActionsAlso) { OdDbObjectIdArray actions = static_cast(object.get())->getActions(); for (OdDbObjectIdArray::const_iterator it = actions.begin(), end = actions.end(); it != end; ++it) doRequest(*it, newStatus, ownedActionsAlso); } } else if (object->isKindOf(OdDbAssocActionBody::desc())) { doRequest(object->ownerId(), newStatus, ownedActionsAlso); } else if (object->isKindOf(OdDbAssocDependency::desc())) static_cast(object.get())->setStatus(newStatus); else { OdDbAssocDependencyPtr dep = OdDbAssocDependency::getFirstDependencyOnObject(object.get()).openObject(); while (dep.get()) { doRequest(dep->objectId(), newStatus, ownedActionsAlso); dep = dep->nextDependencyOnObject().openObject(OdDb::kForWrite); } } } } static RequestedToEvaluate* getFromDb(OdDbDatabase* db) { return static_cast( OdDbObjectReactor::findReactor(db, RequestedToEvaluate::desc())); } void addToDb(OdDbDatabase* db) { db->OdDbObject::addReactor(this); } static void removeFromDb(OdDbDatabase* db) { RequestedToEvaluate* r = getFromDb(db); if (r) db->OdDbObject::removeReactor(r); } public: ODRX_DECLARE_MEMBERS(RequestedToEvaluate); static OdResult requestToEvaluate(const OdDbObjectId& objectId, OdDbAssocStatus newStatus, bool ownedActionsAlso) { if(!isEvaluationRequest(newStatus) && newStatus!=kErasedAssocStatus) return eInvalidInput; OdDbDatabase* db = objectId.database(); if (!db) return eNoDatabase; RequestedToEvaluate* rte = getFromDb(db); if (rte) { if (!rte->saveRequestToEvaluate(objectId, newStatus, ownedActionsAlso)) rte->needsToEvaluate(objectId, newStatus, ownedActionsAlso); return eOk; } else { doRequest(objectId, newStatus, ownedActionsAlso); } return eOk; } bool checkInfiniteLoop() { size_t oldsize = allActionsEvaluated.size(); for (Id2Request::const_iterator it = buf.begin(), end = buf.end(); it != end; ++it) allActionsEvaluated.insert(it->first); return oldsize == allActionsEvaluated.size(); // no new actions involved, so that's it } bool applyPostponedRequests() { if (buf.size()) { m_actionsToEvaluate.clear(); m_ids.clear(); bool infiniteLoop = checkInfiniteLoop(); if (!infiniteLoop) { ODA_TRACE0("************* Applying Postponed Requests *************\n"); } else { ODA_ASSERT_ONCE(!("Prevented infinite loop in assoc.network evaluation. See debug output for detailes.")); ODA_TRACE0("* Prevented infinite loop: the following postponed requests were ignored:\n"); } applyNewStates(); return !infiniteLoop; } return false; } void applyNewStates() { std::for_each(buf.begin(), buf.end(), &processRequest); buf.clear(); } Id2Request postponedRequests() const { return buf; } static bool saveRequestToEvaluate(const OdDbObjectId& objectId, OdDbAssocStatus newStatus, bool ownedActionsAlso) { OdDbDatabase* db = objectId.database(); if (db) { RequestedToEvaluate* rte = AssocNetEvalGraph::getFromDb(db); if (rte && rte->m_isEvaluationInProgress) { ODA_TRACE0("postponed : "); DBG_DUMP_OBJID(objectId); DBG_DUMP_STATUS(newStatus); ODA_TRACE(ownedActionsAlso ? L" ownedActionsAlso" : L""); ODA_TRACE0("\n"); rte->buf.insert(std::pair >( objectId, std::pair(newStatus, ownedActionsAlso) ) ); return true; } } return false; } inline static bool isPostponingEvaluationRequest(OdDbDatabase* db) { if (db) { RequestedToEvaluate* rte = AssocNetEvalGraph::getFromDb(db); if (rte && rte->m_isEvaluationInProgress) return rte->m_isEvaluationInProgress; } return false; } }; void RequestedToEvaluate_rxInit() { RequestedToEvaluate::rxInit(); } void RequestedToEvaluate_rxUninit() { RequestedToEvaluate::rxUninit(); } bool OdDbAssocManager_saveRequestToEvaluate(const OdDbObjectId& objectId, OdDbAssocStatus newStatus, bool ownedActionsAlso) { return AssocNetEvalGraph::saveRequestToEvaluate(objectId, newStatus, ownedActionsAlso); } inline bool OdDbAssocManager_isPostponingEvaluationRequests(OdDbDatabase* db) { return AssocNetEvalGraph::isPostponingEvaluationRequest(db); } OdResult OdDbAssocManager::requestToEvaluate(const OdDbObjectId& objectId, OdDbAssocStatus newStatus, bool ownedActionsAlso) { return RequestedToEvaluate::requestToEvaluate(objectId, newStatus, ownedActionsAlso); } ODRX_NO_CONS_DEFINE_MEMBERS(RequestedToEvaluate, OdDbObjectReactor); void OdDbImpAssocNetwork::evaluate(OdDbAssocAction *pThisNetwork, OdDbAssocEvaluationCallback* pEvaluationCallback) { OdDbAssocNetworkPtr owningNet = OdDbAssocNetwork::cast(owningNetwork().openObject()); if (owningNet.isNull() || !owningNet->isActionEvaluationInProgress()) { // top level network AssocNetEvalGraph evalGraph(pThisNetwork->database()); do { evalGraph.assembly(static_cast(pThisNetwork)); if (pEvaluationCallback) pEvaluationCallback->allDependentActionsMarkedToEvaluate(static_cast(pThisNetwork)); evalGraph.evaluateActions(static_cast(pThisNetwork)); } while (evalGraph.applyPostponedRequests()); // some actions may stay not evaluated due to evaluating code (library) absence // so keep them marked 'changed' to be evaluated once after evaluating code is available evalGraph.unsupressNotEvaluatedActions(); } if (m_actionsToEvaluate.empty()) setStatus(pThisNetwork, m_actions.empty() ? kErasedAssocStatus : kIsUpToDateAssocStatus, true, false); } unsigned int removeAll(const OdDbObjectId& id, OdDbObjectIdArray &from, unsigned int start = 0) { unsigned int found, removed = 0; while (start < from.size() && from.find(id, found, start)) { from.removeAt(found); start = found; ++removed; } return removed; } void OdDbImpAssocNetwork::audit(OdDbObject* pObj, OdDbAuditInfo* pAuditInfo) { OdDbImpAssocAction::audit(pObj, pAuditInfo); auditEvaluationQueue(pObj, pAuditInfo); OdDb::OpenMode mode = pAuditInfo->fixErrors() ? OdDb::kForWrite : OdDb::kForRead; std::set toRemove; for (const auto& item : m_actions) { OdDbObjectId id = item.first; OdDbObjectPtr pActionObj = id.openObject(mode); if (pActionObj.isNull()) { toRemove.insert(id); } else { // For permanently erased object with Null objectId pActionObj->isErased() returns false if(pActionObj->objectId().isErased()) toRemove.insert(id); } } for (const OdDbObjectId id : toRemove) { m_actions.erase(id); m_actionsToEvaluate.erase(id); } } static OdDbObjectId getOrCreateExtensionDict(OdDbObject& obj) { OdDbObjectId extDictId = obj.extensionDictionary(); if (extDictId.isNull()) { obj.createExtensionDictionary(); extDictId = obj.extensionDictionary(); } return extDictId; } static OdDbObjectId getOrCreateAssocNetworkDict(OdDbDictionary& extDict) { const OdString ACAD_ASSOCNETWORK = OD_T("ACAD_ASSOCNETWORK"); OdDbObjectId netDictId = extDict.getAt(ACAD_ASSOCNETWORK); if (netDictId.isNull()) { OdDbDictionaryPtr pNetDict = OdDbDictionary::createObject(); netDictId = extDict.setAt(ACAD_ASSOCNETWORK, pNetDict); } return netDictId; } void OdDbImpAssocNetwork::appendToOwner(OdDbAssocAction* pThisAction, OdDbIdPair& idPair, OdDbObject* owner, OdDbIdMapping& idMap) { ODA_ASSERT(!idPair.isOwnerXlated()); if (owner && owner->isA()->isDerivedFrom(OdDbBlockTableRecord::desc())) { const OdDbObjectId extDictId = getOrCreateExtensionDict(*owner); OdDbDictionaryPtr pExtDict = OdDbDictionary::cast(extDictId.openObject(OdDb::kForWrite)); if (pExtDict.get()) { const OdDbObjectId netDictId = getOrCreateAssocNetworkDict(*pExtDict); OdDbDictionaryPtr pNetDict = OdDbDictionary::cast(netDictId.openObject(OdDb::kForWrite)); if (pNetDict.get()) { const OdString ACAD_ASSOCNETWORK = OD_T("ACAD_ASSOCNETWORK"); OdDbObjectId existingNetId = pNetDict->getAt(ACAD_ASSOCNETWORK); if (!existingNetId.isNull()) { ODA_ASSERT(false && "Cannot append assoc network to a dictionary already containing one"); return; } pNetDict->setAt(ACAD_ASSOCNETWORK, pThisAction); idPair.setOwnerXlated(true); idMap.assign(idPair); OdDbObjectPtr original = idPair.key().openObject(); if (original.get()) { OdDbIdPair ownerMapping(original->ownerId()); if (idMap.compute(ownerMapping) && !ownerMapping.isCloned()) { ownerMapping.setValue(netDictId); idMap.assign(ownerMapping); } } } } } else { pThisAction->OdDbObject::appendToOwner(idPair, owner, idMap); } } OdResult OdDbImpAssocNetwork::postProcessAfterDeepClone(OdDbAssocAction *pAction, OdDbIdMapping& idMap) { OdDbAssocActionPtr clonedAction = oddbTranslate(pAction->objectId(),idMap).openObject(OdDb::kForWrite); if(clonedAction.get()) { if(OdDbBlockTableRecord::cast(objectThatOwnsNetworkInstance(clonedAction).openObject()).isNull()) { // orphaned block record clonedAction->setStatus(kErasedAssocStatus); OdDbAssocNetworkPtr net = clonedAction->owningNetwork().openObject(OdDb::kForWrite); if(net.get()) net->removeAction(clonedAction->objectId(), true); } } return eOk; } void reportAuditError(OdDbAuditInfo* ctx, OdDbObject* obj, OdUInt32 sidValueName, OdUInt32 sidValidation, OdUInt32 sidRepair, OdUInt32 errFound = 1, OdUInt32 errFixed = 0); void OdDbImpAssocNetwork::auditEvaluationQueue(OdDbObject* obj, OdDbAuditInfo* ctx) { if(m_actionsToEvaluate.size()==0) return; bool fix = ctx->fixErrors(); size_t errors = 0; if (fix) { errors = m_actionsToEvaluate.erase(OdDbObjectId::kNull); } else { if (m_actionsToEvaluate.find(OdDbObjectId::kNull)!=m_actionsToEvaluate.end()) ++errors; } if(!m_actionsToEvaluate.empty()) { OdDbAssocActionPtr net = obj; if (!isEvaluationRequest(net->status())) { ++errors; if (fix) net->setStatus(kChangedDirectlyAssocStatus); } } if (errors) reportAuditError(ctx, obj, sidEvaluationQueue, sidVarValidInvalid, sidVarDefRepair, 1, fix ? 1 : 0); } bool OdDbImpAssocNetwork::fixStatusConsistency(OdDbAssocAction* self) { if (m_fixStatusConsistency) { bool hasDirtyActions = false; for (const OdDbObjectId id : m_actionsToEvaluate) { OdDbAssocActionPtr action = OdDbAssocAction::cast(id.openObject()); if (action.get() && action->status() == kIsUpToDateAssocStatus) { action->upgradeOpen(); action->setStatus(kChangedDirectlyAssocStatus, false); ODA_TRACE(L"Status consistency fix: 'changed directly' : "); DBG_DUMP_OBJID(action->objectId()); ODA_TRACE(L"\n"); hasDirtyActions = true; } } for (const auto& item : m_actions) { OdDbAssocActionPtr action = OdDbAssocAction::cast( item.first.openObject() ); if (action.get()) hasDirtyActions |= ::odaaFixStatusConsistency(action); } if(hasDirtyActions) { self->upgradeOpen(); self->setStatus(kChangedDirectlyAssocStatus); ODA_TRACE(L"Status consistency fix: 'changed directly' : "); DBG_DUMP_OBJID(self->objectId()); ODA_TRACE(L"\n"); } m_fixStatusConsistency = false; return hasDirtyActions; } return false; } void OdDbImpAssocNetwork::getDependentActionsToEvaluate(const OdDbAssocAction* pThisNetwork, OdDbActionsToEvaluateCallback* pActionsToEvaluateCallback) const { if (isEvaluationRequest(status())) { OdDbImpAssocAction::getDependentActionsToEvaluate(pThisNetwork, pActionsToEvaluateCallback); OdDbObjectIdArray actionsToEvaluate = static_cast(pThisNetwork)->getActionsToEvaluate(); for (const OdDbObjectId id : actionsToEvaluate) { OdDbAssocActionPtr action = OdDbAssocAction::cast(id.openObject(OdDb::kForWrite)); if (action.get()) pActionsToEvaluateCallback->actionNeedsToEvaluate(action, action->status()); } } } OdDbAssocEvaluationPriority OdDbImpAssocNetwork::evaluationPriority() const { if (m_actionsToEvaluate.empty()) return OdDbAssocEvaluationPriority(kCanBeEvaluatedAssocEvaluationPriority / 2 + evaluationRequestSeverityLevel(status())); return OdDbAssocEvaluationPriority(kCannotBeEvaluatedAssocEvaluationPriority / 2 - m_actionsToEvaluate.size()); } void OdDbImpAssocNetwork::auditAssociativeData(OdDbAssocAction* pThisNetwork) { OdDbImpAssocAction::auditAssociativeData(pThisNetwork); bool topLevelNetwork = (OdDbAssocNetwork::getInstanceFromDatabase(pThisNetwork->database(), false) == pThisNetwork->objectId()); OdDbObjectIdArray actions = static_cast(pThisNetwork)->getActions(); for (OdDbObjectId id : actions) { OdDbAssocActionPtr pAction = OdDbAssocAction::cast(id.openObject(OdDb::kForWrite)); if (pAction) { if (topLevelNetwork) { OdDbAssocNetworkPtr subnet = OdDbAssocNetwork::cast(pAction); if (!subnet.isNull() && subnet->objectThatOwnsNetworkInstance().isErased()) { removeAction(static_cast(pThisNetwork), id, true); ODA_TRACE(L"auditAssociativeData() removed 'zombie' assoc.network: "); DBG_DUMP_OBJID(id); ODA_TRACE(L"\n"); continue; // do not audit "zombie" network } } pAction->assertReadEnabled(); OdDbImpAssocAction* pImpl = OdDbImpAssocAction::getImpObject(pAction); pImpl->auditAssociativeData(pAction); } } }