From e9c52f570f25a2b28a285e10967a5be1dc4d52c5 Mon Sep 17 00:00:00 2001 From: howsohazard <143410553+howsohazard@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:25:39 -0400 Subject: [PATCH] 10430: Completes multithreading support for all opcodes and reentrancy, MINOR (#160) Co-authored-by: Cade Mack <24661281+cademack@users.noreply.github.com> --- src/Amalgam/AssetManager.h | 1 + src/Amalgam/entity/Entity.cpp | 12 ++ src/Amalgam/entity/Entity.h | 180 +++--------------- .../evaluablenode/EvaluableNodeManagement.h | 11 +- .../EvaluableNodeTreeFunctions.cpp | 111 +++++++++++ .../EvaluableNodeTreeFunctions.h | 129 +++++++++---- src/Amalgam/importexport/FileSupportCAML.cpp | 1 + src/Amalgam/importexport/FileSupportCAML.h | 1 + src/Amalgam/interpreter/Interpreter.h | 23 +++ .../InterpreterOpcodesCodeMixing.cpp | 103 ++++------ src/Amalgam/out.txt | 24 +-- 11 files changed, 318 insertions(+), 278 deletions(-) diff --git a/src/Amalgam/AssetManager.h b/src/Amalgam/AssetManager.h index 71dc1eed..23a43ab4 100644 --- a/src/Amalgam/AssetManager.h +++ b/src/Amalgam/AssetManager.h @@ -13,6 +13,7 @@ //system headers: #include #include +#include const std::string FILE_EXTENSION_AMLG_METADATA("mdam"); const std::string FILE_EXTENSION_AMALGAM("amlg"); diff --git a/src/Amalgam/entity/Entity.cpp b/src/Amalgam/entity/Entity.cpp index 156b9bf3..22afeabd 100644 --- a/src/Amalgam/entity/Entity.cpp +++ b/src/Amalgam/entity/Entity.cpp @@ -801,6 +801,18 @@ StringInternPool::StringID Entity::GetContainedEntityIdFromIndex(size_t entity_i return contained_entities[entity_index]->GetIdStringId(); } +Entity *Entity::GetContainedEntityFromIndex(size_t entity_index) +{ + if(!hasContainedEntities) + return nullptr; + + if(entity_index >= entityRelationships.relationships->containedEntities.size()) + return nullptr; + + //look up the pointer by its index + return entityRelationships.relationships->containedEntities[entity_index]; +} + void Entity::CreateQueryCaches() { EnsureHasContainedEntities(); diff --git a/src/Amalgam/entity/Entity.h b/src/Amalgam/entity/Entity.h index 8a599941..1082c8f7 100644 --- a/src/Amalgam/entity/Entity.h +++ b/src/Amalgam/entity/Entity.h @@ -71,7 +71,6 @@ class EntityReference return entity; } -protected: EntityType *entity; }; @@ -362,7 +361,7 @@ class Entity /// write_listeners is optional, and if specified, will log the event void RemoveContainedEntity(StringInternPool::StringID id, std::vector *write_listeners = nullptr); - //returns the ID for a Entity that is contained by this Entity, null if it does not exist + //returns the Entity contained by this Entity for the given id, null if it does not exist Entity *GetContainedEntity(StringInternPool::StringID id); //returns the entity index for the given id @@ -372,6 +371,9 @@ class Entity //looks up the contained entity's string id based on its index in contained entities list StringInternPool::StringID GetContainedEntityIdFromIndex(size_t entity_index); + //returns the Entity contained by this Entity for the given index, null if it does not exist + Entity *GetContainedEntityFromIndex(size_t entity_index); + //returns true if this entity has one or more contained entities constexpr bool HasContainedEntities() { @@ -538,8 +540,10 @@ class Entity //entities at the same level of depth //returns the thread_local static variable entity[Read|Write]ReferenceBuffer, so results will be invalidated //by subsequent calls + //if include_this_entity is true, it will include the entity in the references template - inline EntityReferenceBufferReference GetAllDeeplyContainedEntityReferencesGroupedByDepth() + inline EntityReferenceBufferReference GetAllDeeplyContainedEntityReferencesGroupedByDepth( + bool include_this_entity = false) { EntityReferenceBufferReference erbr; if constexpr(std::is_same::value) @@ -547,10 +551,25 @@ class Entity else erbr = EntityReferenceBufferReference(entityReadReferenceBuffer); + if(include_this_entity) + { + if constexpr(std::is_same::value) + entityWriteReferenceBuffer.emplace_back(this); + else + entityReadReferenceBuffer.emplace_back(this); + } + GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse(); return erbr; } + //appends deply contained entity references to erbr + template + void AppendAllDeeplyContainedEntityReferencesGroupedByDepth(EntityReferenceBufferReference &erbr) + { + GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse(); + } + //gets the current state of the random stream in string form inline std::string GetRandomState() { @@ -684,20 +703,6 @@ class Entity } #ifdef MULTITHREAD_SUPPORT - - //TODO 10975: - // * Remove most locks from Entity itself into Interpreter, etc. - // * Make sure there is a lock so the Entity can't be deleted with interpreters running - // * Apply the locking mechanisms below to all appropriate entity operations and use the appropriate Entity*Reference - - //returns true if Entity a should be locked before b - static inline bool ShouldLockEntityABeforeB(Entity *a, Entity *b) - { - if(a == nullptr || b == nullptr) - return true; - return reinterpret_cast(a) < reinterpret_cast(b); - } - //Returns an appropriate lock object for operations on this Entity //Note that it will only lock the Entity's immediate attributes, not contained entities, code, etc. template @@ -705,147 +710,6 @@ class Entity { return LockType(mutex); } - - //Returns a vector of read locks for the whole entity and all contained Entities recursively - template - inline Concurrency::MultipleLockBufferObject CreateDeepEntityReadLocks(std::vector &lock_buffer) - { - CreateDeepEntityLocksRecurse(lock_buffer); - Concurrency::MultipleLockBufferObject mbo(lock_buffer); - return mbo; - } - - //recurively grabs read locks on the whole entity and everything contained - template - void CreateDeepEntityLocksRecurse(std::vector &lock_buffer) - { - //lock this one - lock_buffer.emplace_back(CreateEntityLock()); - - //early out if done - if(!HasContainedEntities()) - return; - - auto &contained_entities = GetContainedEntities(); - - //need to store more - size_t num_contained = contained_entities.size(); - lock_buffer.reserve(lock_buffer.size() + num_contained); - - //collect and sort contained entities by address - std::vector contained_sorted; - contained_sorted.reserve(num_contained); - - for(auto ce : contained_entities) - contained_sorted.push_back(ce); - - std::sort(begin(contained_sorted), end(contained_sorted), - [](Entity *a, Entity *b) - { - return ShouldLockEntityABeforeB(a, b); - } - ); - - //lock all contained entities before proceeding further - for(auto e : contained_sorted) - lock_buffer.emplace_back(e->CreateEntityLock()); - - for(auto e : contained_sorted) - e->CreateDeepEntityLocksRecurse(lock_buffer); - } - - //locks two entities - // locks will be released when the object is destructed - // makes sure there aren't deadlock conditions (circular wait) by consistently locking them in order of memory address - template - class TwoEntityLock - { - public: - TwoEntityLock(Entity *a, Entity *b) - { - //if equal, but not null, just lock one - if(a == b && a != nullptr) - { - entityLockA = a->CreateEntityLock(); - return; - } - - if(ShouldLockEntityABeforeB(a, b)) - { - if(a != nullptr) - entityLockA = a->CreateEntityLock(); - if(b != nullptr) - entityLockB = b->CreateEntityLock(); - } - else - { - if(b != nullptr) - entityLockB = b->CreateEntityLock(); - if(a != nullptr) - entityLockA = a->CreateEntityLock(); - } - } - - protected: - LockType entityLockA; - LockType entityLockB; - }; - - //Returns a vector of read locks for the whole entity and all contained Entities recursively - template - static inline Concurrency::MultipleLockBufferObject CreateDeepTwoEntityDeepLocks(Entity *a, Entity *b, std::vector &lock_buffer) - { - CreateDeepTwoEntityDeepLocksRecurse(a, b, lock_buffer); - Concurrency::MultipleLockBufferObject mbo(lock_buffer); - return mbo; - } - - template - static inline void CreateDeepTwoEntityDeepLocksRecurse(Entity *a, Entity *b, std::vector &lock_buffer) - { - //handle the cases where one of the entities is nullptr by locking the one that isn't - if(a == nullptr) - { - if(b == nullptr) - return; - - b->CreateDeepEntityLocksRecurse(lock_buffer); - return; - } - - if(b == nullptr) - { - a->CreateDeepEntityLocksRecurse(lock_buffer); - return; - } - - //both a and b are valid - - //if one contains the other, just lock the outer one - if(a->DoesDeepContainEntity(b)) - { - a->CreateDeepEntityLocksRecurse(lock_buffer); - return; - } - if(b->DoesDeepContainEntity(a)) - { - b->CreateDeepEntityLocksRecurse(lock_buffer); - return; - } - - //determine which to lock first - if(ShouldLockEntityABeforeB(a, b)) - { - a->CreateDeepEntityLocksRecurse(lock_buffer); - b->CreateDeepEntityLocksRecurse(lock_buffer); - } - else - { - b->CreateDeepEntityLocksRecurse(lock_buffer); - a->CreateDeepEntityLocksRecurse(lock_buffer); - } - } - #endif //nodes used for storing the entity and for all interpreters for this entity diff --git a/src/Amalgam/evaluablenode/EvaluableNodeManagement.h b/src/Amalgam/evaluablenode/EvaluableNodeManagement.h index 08aeb0c9..d5edce77 100644 --- a/src/Amalgam/evaluablenode/EvaluableNodeManagement.h +++ b/src/Amalgam/evaluablenode/EvaluableNodeManagement.h @@ -32,14 +32,9 @@ class EvaluableNodeReference : value(value), unique(true) { } - constexpr EvaluableNodeReference(double value) - : unique(true) - { - if(FastIsNaN(value)) - this->value = EvaluableNodeImmediateValueWithType(); - else - this->value = EvaluableNodeImmediateValueWithType(value); - } + __forceinline EvaluableNodeReference(double value) + : value(value), unique(true) + { } __forceinline EvaluableNodeReference(StringInternPool::StringID string_id) : value(string_intern_pool.CreateStringReference(string_id)), unique(true) diff --git a/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.cpp b/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.cpp index f7156916..233f7132 100644 --- a/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.cpp +++ b/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.cpp @@ -6,6 +6,7 @@ //system headers: #include #include +#include bool CustomEvaluableNodeComparator::operator()(EvaluableNode *a, EvaluableNode *b) { @@ -77,6 +78,116 @@ std::vector CustomEvaluableNodeOrderedChildNodesSort(std::vecto return list_copy_2; } +std::tuple> + TraverseToDeeplyContainedEntityReadReferencesViaEvaluableNodeIDPath(Entity *from_entity, + EvaluableNode *id_path_1, EvaluableNode *id_path_2) +{ + if(from_entity == nullptr) + return std::make_tuple(nullptr, nullptr, + Entity::EntityReferenceBufferReference()); + + EvaluableNodeIDPathTraverser traverser_1(id_path_1, nullptr); + if(traverser_1.IsEntity()) + { + //lock everything in entity_1, and it will contain everything in entity_2 + auto erbr = from_entity->GetAllDeeplyContainedEntityReferencesGroupedByDepth(true); + Entity *entity_2 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(from_entity, id_path_2); + return std::make_tuple(from_entity, entity_2, std::move(erbr)); + } + + EvaluableNodeIDPathTraverser traverser_2(id_path_2, nullptr); + if(traverser_2.IsEntity()) + { + //lock everything in entity_2, and it will contain everything in entity_1 + auto erbr = from_entity->GetAllDeeplyContainedEntityReferencesGroupedByDepth(true); + Entity *entity_1 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(from_entity, id_path_1); + return std::make_tuple(entity_1, from_entity, std::move(erbr)); + } + + EntityReadReference relative_entity_container(from_entity); + + //infinite loop, but logic inside will break it out appropriately + while(true) + { + EvaluableNode *cur_node_id_1 = traverser_1.GetCurId(); + StringInternPool::StringID sid_1 = EvaluableNode::ToStringIDIfExists(cur_node_id_1); + + EvaluableNode *cur_node_id_2 = traverser_2.GetCurId(); + StringInternPool::StringID sid_2 = EvaluableNode::ToStringIDIfExists(cur_node_id_2); + + if(sid_1 != sid_2) + { + size_t entity_index_1 = relative_entity_container->GetContainedEntityIndex(sid_1); + size_t entity_index_2 = relative_entity_container->GetContainedEntityIndex(sid_2); + + if(entity_index_1 < entity_index_2) + { + EntityReadReference entity_1 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(relative_entity_container, traverser_1); + Entity *entity_1_ptr = entity_1; + auto erbr = entity_1->GetAllDeeplyContainedEntityReferencesGroupedByDepth(false); + erbr->emplace_back(std::move(entity_1)); + + EntityReadReference entity_2 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(relative_entity_container, traverser_2); + Entity *entity_2_ptr = entity_2; + entity_2->AppendAllDeeplyContainedEntityReferencesGroupedByDepth(erbr); + erbr->emplace_back(std::move(entity_2)); + + return std::make_tuple(entity_1_ptr, entity_2_ptr, std::move(erbr)); + } + else + { + EntityReadReference entity_2 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(relative_entity_container, traverser_2); + Entity *entity_2_ptr = entity_2; + auto erbr = entity_2->GetAllDeeplyContainedEntityReferencesGroupedByDepth(false); + erbr->emplace_back(std::move(entity_2)); + + EntityReadReference entity_1 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(relative_entity_container, traverser_1); + Entity *entity_1_ptr = entity_1; + entity_1->AppendAllDeeplyContainedEntityReferencesGroupedByDepth(erbr); + erbr->emplace_back(std::move(entity_1)); + + return std::make_tuple(entity_1_ptr, entity_2_ptr, std::move(erbr)); + } + + break; + } + + if(traverser_1.IsEntity()) + { + //lock everything in entity_1, and it will contain everything in entity_2 + auto erbr = relative_entity_container->GetAllDeeplyContainedEntityReferencesGroupedByDepth(true); + + //both are the same entity + if(traverser_1.IsEntity()) + return std::make_tuple(relative_entity_container.entity, relative_entity_container.entity, std::move(erbr)); + + Entity *entity_2 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(relative_entity_container, traverser_2); + return std::make_tuple(relative_entity_container.entity, entity_2, std::move(erbr)); + } + + if(traverser_2.IsEntity()) + { + //lock everything in entity_2, and it will contain everything in entity_1 + auto erbr = relative_entity_container->GetAllDeeplyContainedEntityReferencesGroupedByDepth(true); + Entity *entity_1 = TraverseToExistingEntityReferenceViaEvaluableNodeIDPath(relative_entity_container, traverser_1); + return std::make_tuple(entity_1, relative_entity_container.entity, std::move(erbr)); + } + + //ids are the same, continue traversing + Entity *next_entity = relative_entity_container->GetContainedEntity(sid_1); + if(next_entity == nullptr) + return std::make_tuple(nullptr, nullptr, + Entity::EntityReferenceBufferReference()); + + relative_entity_container = EntityReadReference(next_entity); + traverser_1.AdvanceIndex(); + traverser_2.AdvanceIndex(); + } + + return std::make_tuple(nullptr, nullptr, + Entity::EntityReferenceBufferReference()); +} + EvaluableNode *GetTraversalIDPathFromAToB(EvaluableNodeManager *enm, Entity *a, Entity *b) { //shouldn't happen, but check diff --git a/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.h b/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.h index 03c4eb8b..a0ead769 100644 --- a/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.h +++ b/src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.h @@ -8,6 +8,7 @@ #include #include #include +#include //forward declarations: class Entity; @@ -39,37 +40,60 @@ class EvaluableNodeIDPathTraverser { public: inline EvaluableNodeIDPathTraverser() - : idPath(nullptr), idPathEntries(nullptr), curIndex(0) + : idPath(nullptr), idPathEntries(nullptr), curIndex(0), destSidReference(nullptr) { } //calls AnalyzeIDPath with the same parameters - inline EvaluableNodeIDPathTraverser(EvaluableNode *id_path, bool has_destination_id) + inline EvaluableNodeIDPathTraverser(EvaluableNode *id_path, StringInternRef *dest_sid_ref) { - AnalyzeIDPath(id_path, has_destination_id); + AnalyzeIDPath(id_path, dest_sid_ref); } //populates attributes based on the id_path - //if has_destination_id, then it will leave one id at the end for the destination - void AnalyzeIDPath(EvaluableNode *id_path, bool has_destination_id) + //if has non-null dest_sid_ref, then it will store the pointer and use it to populate the destination string id + void AnalyzeIDPath(EvaluableNode *id_path, StringInternRef *dest_sid_ref) { - idPath = id_path; - idPathEntries = &idPath->GetOrderedChildNodes(); + idPath = nullptr; + idPathEntries = nullptr; curIndex = 0; containerIdIndex = 0; entityIdIndex = 0; lastIdIndex = 0; + destSidReference = dest_sid_ref; + //if the destination sid is requested, initialize it + if(destSidReference != nullptr) + *destSidReference = StringInternRef(string_intern_pool.NOT_A_STRING_ID); + + //if single value, then just set and return + if(EvaluableNode::IsNull(id_path)) + { + idPath = id_path; + return; + } + else if(id_path->GetType() != ENT_LIST) + { + idPath = id_path; + if(destSidReference == nullptr) + { + entityIdIndex = 1; + lastIdIndex = 1; + } + return; + } + //size of the entity list excluding trailing nulls - size_t non_null_size = idPathEntries->size(); - while(non_null_size > 0 && EvaluableNode::IsNull((*idPathEntries)[non_null_size - 1])) + auto id_path_entries = &id_path->GetOrderedChildNodesReference(); + size_t non_null_size = id_path_entries->size(); + while(non_null_size > 0 && EvaluableNode::IsNull((*id_path_entries)[non_null_size - 1])) non_null_size--; //if no entities, nothing to traverse if(non_null_size == 0) - { - idPathEntries = nullptr; return; - } + + idPath = id_path; + idPathEntries = id_path_entries; //find first index while(curIndex < non_null_size && EvaluableNode::IsNull((*idPathEntries)[curIndex])) @@ -78,7 +102,7 @@ class EvaluableNodeIDPathTraverser lastIdIndex = non_null_size - 1; entityIdIndex = lastIdIndex; - if(has_destination_id) + if(destSidReference != nullptr) { //walk down to find the entity id while(entityIdIndex > curIndex && EvaluableNode::IsNull((*idPathEntries)[entityIdIndex - 1])) @@ -118,7 +142,14 @@ class EvaluableNodeIDPathTraverser //gets the current ID, nullptr if out of ids inline EvaluableNode *GetCurId() { - if(idPathEntries == nullptr || curIndex > entityIdIndex) + if(idPathEntries == nullptr) + { + if(curIndex == 0) + return idPath; + return nullptr; + } + + if(curIndex > entityIdIndex) return nullptr; return (*idPathEntries)[curIndex]; } @@ -138,6 +169,9 @@ class EvaluableNodeIDPathTraverser //index of the last entity id, if applicable size_t lastIdIndex; + + //if not nullptr, then will be set to a reference to the destination string id + StringInternRef *destSidReference; }; template @@ -183,9 +217,9 @@ TraverseToEntityReferenceAndContainerViaEvaluableNodeID(Entity *from_entity, StringInternRef *dest_sid_ref) { if(EvaluableNode::IsNull(id_node_1)) - TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, id_node_2, dest_sid_ref); + return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, id_node_2, dest_sid_ref); if(EvaluableNode::IsNull(id_node_2)) - TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, id_node_1, dest_sid_ref); + return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, id_node_1, dest_sid_ref); if(dest_sid_ref == nullptr) { @@ -228,31 +262,18 @@ TraverseToEntityReferenceAndContainerViaEvaluableNodeID(Entity *from_entity, //Starts at the container specified and traverses the id path specified, finding the relative Entity to from_entity //returns a reference of the entity specified by the id path followed by a reference to its container -//if id_path does not exist or is invalid then returns nullptr for both -//if id_path specifies the entity in from_entity, then it returns a reference to it -//if dest_sid_ref is not null, it will assume it is a location to store a string id with reference -// that must be managed by caller template std::pair TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( - Entity *from_entity, EvaluableNode *id_path, - StringInternRef *dest_sid_ref = nullptr) + Entity *from_entity, EvaluableNodeIDPathTraverser &traverser) { - //if the destination sid is requested, initialize it - if(dest_sid_ref != nullptr) - *dest_sid_ref = StringInternRef(string_intern_pool.NOT_A_STRING_ID); - if(from_entity == nullptr) return std::make_pair(EntityReferenceType(nullptr), EntityReferenceType(nullptr)); - if(EvaluableNode::IsNull(id_path) || id_path->GetType() != ENT_LIST) - return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, id_path, dest_sid_ref); - - EvaluableNodeIDPathTraverser traverser(id_path, dest_sid_ref != nullptr); - //if already at the entity, return if(traverser.IsEntity()) - return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, traverser.GetCurId(), dest_sid_ref); + return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, + traverser.GetCurId(), traverser.destSidReference); //if at the container, lock the container and return the entity if(traverser.IsContainer()) @@ -260,7 +281,8 @@ TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( EvaluableNode *node_id_1 = traverser.GetCurId(); traverser.AdvanceIndex(); EvaluableNode *node_id_2 = traverser.GetCurId(); - return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, node_id_1, node_id_2, dest_sid_ref); + return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(from_entity, + node_id_1, node_id_2, traverser.destSidReference); } //the entity is deeper than one of the container's entities, so put a read lock on it and traverse @@ -285,7 +307,8 @@ TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( EvaluableNode *next_node_id_1 = traverser.GetCurId(); traverser.AdvanceIndex(); EvaluableNode *next_node_id_2 = traverser.GetCurId(); - return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(next_entity, next_node_id_1, next_node_id_2, dest_sid_ref); + return TraverseToEntityReferenceAndContainerViaEvaluableNodeID(next_entity, + next_node_id_1, next_node_id_2, traverser.destSidReference); } //traverse the id path for the next loop @@ -300,16 +323,52 @@ TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( //like TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath //except only returns the entity requested template +inline EntityReferenceType TraverseToExistingEntityReferenceViaEvaluableNodeIDPath( + Entity *from_entity, EvaluableNodeIDPathTraverser &traverser) +{ + auto [entity, container] + = TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( + from_entity, traverser); + + return std::move(entity); +} + +//like corresponding TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath +//but uses an id path and populates dest_sid_ref with the destination string id +// if dest_sid_ref is not nullptr +template +std::pair +TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( + Entity *from_entity, EvaluableNode *id_path, StringInternRef *dest_sid_ref = nullptr) +{ + EvaluableNodeIDPathTraverser traverser(id_path, dest_sid_ref); + auto [entity, container] + = TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( + from_entity, traverser); + + return std::make_pair(std::move(entity), std::move(container)); +} + +//like corresponding TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath +//but uses an id path +template inline EntityReferenceType TraverseToExistingEntityReferenceViaEvaluableNodeIDPath( Entity *from_entity, EvaluableNode *id_path) { + EvaluableNodeIDPathTraverser traverser(id_path, nullptr); auto [entity, container] = TraverseToEntityReferenceAndContainerViaEvaluableNodeIDPath( - from_entity, id_path); + from_entity, traverser); return std::move(entity); } +//traverses id_path_1 and id_path_2 from from_entity, returns the corresponding entities, as well as +//read references to those entities and all entities they contain +std::tuple> + TraverseToDeeplyContainedEntityReadReferencesViaEvaluableNodeIDPath(Entity *from_entity, + EvaluableNode *id_path_1, EvaluableNode *id_path_2); + //constructs an ID or list of IDs that will traverse frome a to b, assuming that b is contained somewhere within a EvaluableNode *GetTraversalIDPathFromAToB(EvaluableNodeManager *enm, Entity *a, Entity *b); diff --git a/src/Amalgam/importexport/FileSupportCAML.cpp b/src/Amalgam/importexport/FileSupportCAML.cpp index de5f744b..c44197f0 100644 --- a/src/Amalgam/importexport/FileSupportCAML.cpp +++ b/src/Amalgam/importexport/FileSupportCAML.cpp @@ -11,6 +11,7 @@ #include #include #include +#include //magic number written at beginning of CAML file static const uint8_t s_magic_number[] = { 'c', 'a', 'm', 'l' }; diff --git a/src/Amalgam/importexport/FileSupportCAML.h b/src/Amalgam/importexport/FileSupportCAML.h index b2c7919b..49de3fef 100644 --- a/src/Amalgam/importexport/FileSupportCAML.h +++ b/src/Amalgam/importexport/FileSupportCAML.h @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace FileSupportCAML diff --git a/src/Amalgam/interpreter/Interpreter.h b/src/Amalgam/interpreter/Interpreter.h index f93ed85e..8ff07c75 100644 --- a/src/Amalgam/interpreter/Interpreter.h +++ b/src/Amalgam/interpreter/Interpreter.h @@ -450,6 +450,29 @@ class Interpreter return InterpretNodeIntoRelativeSourceEntityReference(node_id_path_to_interpret); } + //like InterpretNodeIntoRelativeSourceEntityReference, but a pair of read references + inline std::tuple> + InterpretNodeIntoRelativeSourceEntityReadReferences(EvaluableNode *node_id_path_to_interpret_1, EvaluableNode *node_id_path_to_interpret_2) + { + if(curEntity == nullptr) + return std::make_tuple(nullptr, nullptr, + Entity::EntityReferenceBufferReference()); + + auto node_id_path_1 = InterpretNodeForImmediateUse(node_id_path_to_interpret_1); + auto node_stack = CreateInterpreterNodeStackStateSaver(node_id_path_1); + auto node_id_path_2 = InterpretNodeForImmediateUse(node_id_path_to_interpret_2); + node_stack.PopEvaluableNode(); + + auto [entity_1, entity_2, erbr] + = TraverseToDeeplyContainedEntityReadReferencesViaEvaluableNodeIDPath(curEntity, + node_id_path_1, node_id_path_2); + + evaluableNodeManager->FreeNodeTreeIfPossible(node_id_path_1); + evaluableNodeManager->FreeNodeTreeIfPossible(node_id_path_2); + + return std::make_tuple(entity_1, entity_2, std::move(erbr)); + } + protected: //Traverses down n until it reaches the furthest-most nodes from top_node, then bubbles back up re-evaluating each node via the specified function diff --git a/src/Amalgam/interpreter/InterpreterOpcodesCodeMixing.cpp b/src/Amalgam/interpreter/InterpreterOpcodesCodeMixing.cpp index f27fa6c7..ea4488e2 100644 --- a/src/Amalgam/interpreter/InterpreterOpcodesCodeMixing.cpp +++ b/src/Amalgam/interpreter/InterpreterOpcodesCodeMixing.cpp @@ -424,15 +424,8 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_COMMONALITY_ENTITIES(Evalu if(ocn.size() < 2) return EvaluableNodeReference::Null(); - //TODO 10975: change this to lock all entities at once - //get the id of the first source entity - Entity *source_entity_1 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[0]); - if(source_entity_1 == nullptr) - return EvaluableNodeReference::Null(); - - //get the id of the second source entity - Entity *source_entity_2 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[1]); - if(source_entity_2 == nullptr) + auto [source_entity_1, source_entity_2, erbr] = InterpretNodeIntoRelativeSourceEntityReadReferences(ocn[0], ocn[1]); + if(source_entity_1 == nullptr || source_entity_2 == nullptr) return EvaluableNodeReference::Null(); auto commonality = EntityManipulation::NumberOfSharedNodes(source_entity_1, source_entity_2); @@ -446,15 +439,8 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_EDIT_DISTANCE_ENTITIES(Eva if(ocn.size() < 2) return EvaluableNodeReference::Null(); - //TODO 10975: change this to lock all entities at once - //get the id of the first source entity - Entity *source_entity_1 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[0]); - if(source_entity_1 == nullptr) - return EvaluableNodeReference::Null(); - - //get the id of the second source entity - Entity *source_entity_2 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[1]); - if(source_entity_2 == nullptr) + auto [source_entity_1, source_entity_2, erbr] = InterpretNodeIntoRelativeSourceEntityReadReferences(ocn[0], ocn[1]); + if(source_entity_1 == nullptr || source_entity_2 == nullptr) return EvaluableNodeReference::Null(); double edit_distance = EntityManipulation::EditDistance(source_entity_1, source_entity_2); @@ -472,19 +458,20 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_INTERSECT_ENTITIES(Evaluab if(curEntity == nullptr) return EvaluableNodeReference::Null(); - //TODO 10975: change this to lock all entities at once - //get the id of the first source entity - Entity *source_entity_1 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[0]); - //need a source entity, and can't copy self! (that could cause badness) - if(source_entity_1 == nullptr || source_entity_1 == curEntity) + auto [source_entity_1, source_entity_2, erbr] = InterpretNodeIntoRelativeSourceEntityReadReferences(ocn[0], ocn[1]); + if(source_entity_1 == nullptr || source_entity_2 == nullptr) return EvaluableNodeReference::Null(); - //get the id of the second source entity - Entity *source_entity_2 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[1]); - //need a source entity, and can't copy self! (that could cause badness) - if(source_entity_2 == nullptr || source_entity_2 == curEntity) + //need a source entity, and shouldn't copy self + if(source_entity_1 == curEntity || source_entity_2 == curEntity) return EvaluableNodeReference::Null(); + //create new entity by merging + Entity *new_entity = EntityManipulation::IntersectEntities(this, source_entity_1, source_entity_2); + + //no longer need entity references + erbr.Clear(); + //get destination if applicable EntityWriteReference destination_entity_parent; StringInternRef new_entity_id; @@ -496,9 +483,6 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_INTERSECT_ENTITIES(Evaluab if(destination_entity_parent == nullptr) return EvaluableNodeReference::Null(); - //create new entity by merging - Entity *new_entity = EntityManipulation::IntersectEntities(this, source_entity_1, source_entity_2); - //accumulate usage if(!AllowUnlimitedExecutionNodes()) curNumExecutionNodesAllocatedToEntities += new_entity->GetDeepSizeInNodes(); @@ -528,19 +512,20 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_UNION_ENTITIES(EvaluableNo if(curEntity == nullptr) return EvaluableNodeReference::Null(); - //TODO 10975: change this to lock all entities at once - //get the id of the first source entity - Entity *source_entity_1 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[0]); - //need a source entity, and can't copy self! (that could cause badness) - if(source_entity_1 == nullptr || source_entity_1 == curEntity) + auto [source_entity_1, source_entity_2, erbr] = InterpretNodeIntoRelativeSourceEntityReadReferences(ocn[0], ocn[1]); + if(source_entity_1 == nullptr || source_entity_2 == nullptr) return EvaluableNodeReference::Null(); - //get the id of the second source entity - Entity *source_entity_2 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[1]); - //need a source entity, and can't copy self! (that could cause badness) - if(source_entity_2 == nullptr || source_entity_2 == curEntity) + //need a source entity, and shouldn't copy self + if(source_entity_1 == curEntity || source_entity_2 == curEntity) return EvaluableNodeReference::Null(); + //create new entity by merging + Entity *new_entity = EntityManipulation::UnionEntities(this, source_entity_1, source_entity_2); + + //no longer need entity references + erbr.Clear(); + //get destination if applicable EntityWriteReference destination_entity_parent; StringInternRef new_entity_id; @@ -552,9 +537,6 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_UNION_ENTITIES(EvaluableNo if(destination_entity_parent == nullptr) return EvaluableNodeReference::Null(); - //create new entity by merging - Entity *new_entity = EntityManipulation::UnionEntities(this, source_entity_1, source_entity_2); - //accumulate usage if(!AllowUnlimitedExecutionNodes()) curNumExecutionNodesAllocatedToEntities += new_entity->GetDeepSizeInNodes(); @@ -580,17 +562,12 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_DIFFERENCE_ENTITIES(Evalua if(ocn.size() < 2) return EvaluableNodeReference::Null(); - //TODO 10975: change this to lock all entities at once - //get the id of the first source entity - Entity *entity_1 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[0]); - //need a source entity, and can't copy self! (that could cause badness) - if(entity_1 == nullptr || entity_1 == curEntity) + auto [entity_1, entity_2, erbr] = InterpretNodeIntoRelativeSourceEntityReadReferences(ocn[0], ocn[1]); + if(entity_1 == nullptr || entity_2 == nullptr) return EvaluableNodeReference::Null(); - //get the id of the second source entity - Entity *entity_2 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[1]); - //need a source entity, and can't copy self! (that could cause badness) - if(entity_2 == nullptr || entity_2 == curEntity) + //can't difference with self + if(entity_1 == curEntity || entity_2 == curEntity) return EvaluableNodeReference::Null(); return EntityManipulation::DifferenceEntities(this, entity_1, entity_2); @@ -627,28 +604,20 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_MIX_ENTITIES(EvaluableNode if(ocn.size() > 5) fraction_unnamed_entities_to_mix = InterpretNodeIntoNumberValue(ocn[5]); - //TODO 10975: change this to lock all entities at once - //retrieve the entities after other parameters to minimize time in locks - // and prevent deadlock if one of the params accessed the entity - //get the id of the first source entity - Entity* source_entity_1 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[0]); - //need a source entity, and can't copy self! (that could cause badness) - if(source_entity_1 == nullptr || source_entity_1 == curEntity) + auto [source_entity_1, source_entity_2, erbr] = InterpretNodeIntoRelativeSourceEntityReadReferences(ocn[0], ocn[1]); + if(source_entity_1 == nullptr || source_entity_2 == nullptr) return EvaluableNodeReference::Null(); - //get the id of the second source entity - Entity* source_entity_2 = InterpretNodeIntoRelativeSourceEntityReadReference(ocn[1]); - //need a source entity, and can't copy self! (that could cause badness) - if(source_entity_2 == nullptr || source_entity_2 == curEntity) + //need a source entity, and shouldn't copy self + if(source_entity_1 == curEntity || source_entity_2 == curEntity) return EvaluableNodeReference::Null(); //create new entity by merging Entity *new_entity = EntityManipulation::MixEntities(this, source_entity_1, source_entity_2, blend1, blend2, similar_mix_chance, fraction_unnamed_entities_to_mix); - //accumulate usage - if(!AllowUnlimitedExecutionNodes()) - curNumExecutionNodesAllocatedToEntities += new_entity->GetDeepSizeInNodes(); + //no longer need entity references + erbr.Clear(); //get destination if applicable EntityWriteReference destination_entity_parent; @@ -661,6 +630,10 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_MIX_ENTITIES(EvaluableNode if(destination_entity_parent == nullptr) return EvaluableNodeReference::Null(); + //accumulate usage + if(!AllowUnlimitedExecutionNodes()) + curNumExecutionNodesAllocatedToEntities += new_entity->GetDeepSizeInNodes(); + destination_entity_parent->AddContainedEntityViaReference(new_entity, new_entity_id, writeListeners); if(new_entity_id == StringInternPool::NOT_A_STRING_ID) diff --git a/src/Amalgam/out.txt b/src/Amalgam/out.txt index 2472791f..7fab71d2 100644 --- a/src/Amalgam/out.txt +++ b/src/Amalgam/out.txt @@ -1263,7 +1263,7 @@ current_index: 2 8 ] accum_string "abcdef" - argv ["C:\\Users\\Chris Hazard\\Desktop\\Howso_repos\\amalgam\\src\\Amalgam\\./amlg_code/full_test.amlg"] + argv ["C:\\Users\\ChristopherHazard\\Desktop\\Howso_repos\\amalgam\\src\\Amalgam\\./amlg_code/full_test.amlg"] bar (declare {x 6} (+ x 2) @@ -1276,10 +1276,10 @@ current_index: 2 A {B 2} B 2 } - interpreter "C:\\Users\\Chris Hazard\\Desktop\\Howso_repos\\amalgam\\x64\\MT_Release_EXE\\Amalgam.exe" + interpreter "C:\\Users\\ChristopherHazard\\Desktop\\Howso_repos\\amalgam\\x64\\MT_Release_EXE\\Amalgam.exe" raaa 2 rwww 1 - start_time 1719346356.42301 + start_time 1719353144.303555 www 1 x 12 zz 10 @@ -1306,7 +1306,7 @@ current_index: 2 8 ] accum_string "abcdef" - argv ["C:\\Users\\Chris Hazard\\Desktop\\Howso_repos\\amalgam\\src\\Amalgam\\./amlg_code/full_test.amlg"] + argv ["C:\\Users\\ChristopherHazard\\Desktop\\Howso_repos\\amalgam\\src\\Amalgam\\./amlg_code/full_test.amlg"] bar (declare {x 6} (+ x 2) @@ -1319,10 +1319,10 @@ current_index: 2 A {B 2} B 2 } - interpreter "C:\\Users\\Chris Hazard\\Desktop\\Howso_repos\\amalgam\\x64\\MT_Release_EXE\\Amalgam.exe" + interpreter "C:\\Users\\ChristopherHazard\\Desktop\\Howso_repos\\amalgam\\x64\\MT_Release_EXE\\Amalgam.exe" raaa 2 rwww 1 - start_time 1719346356.42301 + start_time 1719353144.303555 www 1 x 12 zz 10 @@ -1348,7 +1348,7 @@ current_index: 2 8 ] accum_string "abcdef" - argv ["C:\\Users\\Chris Hazard\\Desktop\\Howso_repos\\amalgam\\src\\Amalgam\\./amlg_code/full_test.amlg"] + argv ["C:\\Users\\ChristopherHazard\\Desktop\\Howso_repos\\amalgam\\src\\Amalgam\\./amlg_code/full_test.amlg"] bar (declare {x 6} (+ x 2) @@ -1361,10 +1361,10 @@ current_index: 2 A {B 2} B 2 } - interpreter "C:\\Users\\Chris Hazard\\Desktop\\Howso_repos\\amalgam\\x64\\MT_Release_EXE\\Amalgam.exe" + interpreter "C:\\Users\\ChristopherHazard\\Desktop\\Howso_repos\\amalgam\\x64\\MT_Release_EXE\\Amalgam.exe" raaa 2 rwww 1 - start_time 1719346356.42301 + start_time 1719353144.303555 www 1 x 12 zz 10 @@ -1630,7 +1630,7 @@ e: - .inf 25: {a 1} -current date-time in epoch: 2024-06-25-16.12.36.7313240 +current date-time in epoch: 2024-06-25-18.05.44.3895120 2020-06-07 00:22:59 1391230800 1391230800 @@ -3451,7 +3451,7 @@ deep sets --set_entity_root_permission-- RootTest -1719346356.940955 +1719353144.664102 (true) RootTest @@ -4694,4 +4694,4 @@ concurrent entity writes successful: (true) --clean-up test files-- --total execution time-- -1.2933847904205322 +1.7899150848388672