diff --git a/docs/language.js b/docs/language.js index 3214efcd..7aa064b5 100644 --- a/docs/language.js +++ b/docs/language.js @@ -27,10 +27,10 @@ var data = [ }, { - "parameter" : "reclaim_resources [id_path entity] [bool apply_to_all_contained_entities] [bool clear_query_caches] [bool collect_garbage] [bool force_free_memory] ", + "parameter" : "reclaim_resources [id_path entity] [bool apply_to_all_contained_entities] [bool|list clear_query_caches] [bool collect_garbage] [bool force_free_memory] ", "output" : "*", "permissions" : "alter_performance", - "description" : "Frees resources of the specified types on entity, which is the current entity if null, and will include all contained entities if apply_to_all_contained_entities is true, which defaults to false, though the opcode will be unable to complete if there are concurrent threads running on any of the contained entities. The parameter clear_query_caches will remove the query caches, which will make it faster to add, remove, or edit contained entities, but the cache will be rebuilt once a query is called. The parameter collect_garbage will perform garbage collection on the entity, and if force_free_memory is true, it will reallocate memory buffers to their current size, after garbage collection if both are specified.", + "description" : "Frees resources of the specified types on entity, which is the current entity if null, and will include all contained entities if apply_to_all_contained_entities is true, which defaults to false, though the opcode will be unable to complete if there are concurrent threads running on any of the contained entities. The parameter clear_query_caches will remove the query caches, which will make it faster to add, remove, or edit contained entities, but the cache will be rebuilt once a query is called. If clear_query_caches is a boolean, then it will either clear all the caches or none. If clear_query_caches is a list of strings, then it will only clear caches for the labels corresponding to the strings in the list. The parameter collect_garbage will perform garbage collection on the entity, and if force_free_memory is true, it will reallocate memory buffers to their current size, after garbage collection if both are specified.", "example" : "(reclaim_resources (null) .true .false .true .false)" }, diff --git a/src/Amalgam/SeparableBoxFilterDataStore.h b/src/Amalgam/SeparableBoxFilterDataStore.h index 7ba572b7..f7924d68 100644 --- a/src/Amalgam/SeparableBoxFilterDataStore.h +++ b/src/Amalgam/SeparableBoxFilterDataStore.h @@ -140,6 +140,14 @@ class SeparableBoxFilterDataStore } } + //removes the label specified by label_sid + inline void RemoveLabel(StringInternPool::StringID label_sid) + { + size_t column_index = GetColumnIndexFromLabelId(label_sid); + if(column_index < columnData.size()) + RemoveColumnIndex(column_index); + } + //adds an entity to the database void AddEntity(Entity *entity, size_t entity_index); diff --git a/src/Amalgam/amlg_code/full_test.amlg b/src/Amalgam/amlg_code/full_test.amlg index 199fd060..43026b6a 100644 --- a/src/Amalgam/amlg_code/full_test.amlg +++ b/src/Amalgam/amlg_code/full_test.amlg @@ -3827,6 +3827,8 @@ (print "--reclaim_resources--\n") +(reclaim_resources (null) .true ["x"] .true .true ) + (reclaim_resources (null) .true .true .true .true ) (print "--null equality tests--\n") diff --git a/src/Amalgam/entity/Entity.h b/src/Amalgam/entity/Entity.h index f9b48900..a64e92ea 100644 --- a/src/Amalgam/entity/Entity.h +++ b/src/Amalgam/entity/Entity.h @@ -586,18 +586,26 @@ class Entity //when calling this, must ensure that there is a write lock on the entity or that nothing can execute on it inline void ClearQueryCaches() { - if(hasContainedEntities && entityRelationships.relationships->queryCaches) - { - #if defined(MULTITHREAD_SUPPORT) || defined(MULTITHREAD_INTERFACE) - //obtain a write lock and immediately release it to make sure there aren't any operations - //waiting to complete. don't need to worry about new operations as they will not be able - //to start with a write lock on this entity - Concurrency::WriteLock write_lock(entityRelationships.relationships->queryCaches->mutex); - write_lock.release(); - #endif + if(!HasQueryCaches()) + return; + + #if defined(MULTITHREAD_SUPPORT) || defined(MULTITHREAD_INTERFACE) + //obtain a write lock and immediately release it to make sure there aren't any operations + //waiting to complete. don't need to worry about new operations as they will not be able + //to start with a write lock on this entity + Concurrency::WriteLock write_lock(entityRelationships.relationships->queryCaches->mutex); + write_lock.release(); + #endif - entityRelationships.relationships->queryCaches.reset(); - } + entityRelationships.relationships->queryCaches.reset(); + } + + inline void ClearQueryCacheForLabel(StringInternPool::StringID label_sid) + { + if(!HasQueryCaches()) + return; + + entityRelationships.relationships->queryCaches->RemoveLabelFromCache(label_sid); } //creates a cache if it does not exist @@ -607,9 +615,9 @@ class Entity //returns a nullptr if does not have an active cache inline EntityQueryCaches *GetQueryCaches() { - if(hasContainedEntities && entityRelationships.relationships->queryCaches) - return entityRelationships.relationships->queryCaches.get(); - return nullptr; + if(!HasQueryCaches()) + return nullptr; + return entityRelationships.relationships->queryCaches.get(); } //returns a pointer to the query caches for this entity's container @@ -745,9 +753,15 @@ class Entity if(this != exclude_entity) { if constexpr(std::is_same::value) + { + if(IsEntityCurrentlyBeingExecuted()) + return erbr; entityWriteReferenceBuffer.emplace_back(this); + } else + { entityReadReferenceBuffer.emplace_back(this); + } } erbr.maxEntityPathDepth++; @@ -965,12 +979,6 @@ class Entity if(!hasContainedEntities) return true; - if constexpr(std::is_same::value) - { - if(IsEntityCurrentlyBeingExecuted()) - return false; - } - auto &contained_entities = GetContainedEntities(); for(Entity *e : contained_entities) { @@ -978,9 +986,16 @@ class Entity continue; if constexpr(std::is_same::value) + { + if(e->IsEntityCurrentlyBeingExecuted()) + return false; + entityWriteReferenceBuffer.emplace_back(e); + } else + { entityReadReferenceBuffer.emplace_back(e); + } } for(auto &ce : contained_entities) diff --git a/src/Amalgam/entity/EntityQueryCaches.h b/src/Amalgam/entity/EntityQueryCaches.h index ecef6050..ac493c88 100644 --- a/src/Amalgam/entity/EntityQueryCaches.h +++ b/src/Amalgam/entity/EntityQueryCaches.h @@ -101,6 +101,15 @@ class EntityQueryCaches void EnsureLabelsAreCached(EntityQueryCondition *cond); #endif + //removes label_sid from the cache + inline void RemoveLabelFromCache(StringInternPool::StringID label_sid) + { + #if defined(MULTITHREAD_SUPPORT) || defined(MULTITHREAD_INTERFACE) + Concurrency::WriteLock write_lock(mutex); + #endif + sbfds.RemoveLabel(label_sid); + } + //returns the set matching_entities of entity ids in the cache that match the provided query condition cond, will fill compute_results with numeric results if KNN query //if is_first is true, optimizes to skip unioning results with matching_entities (just overwrites instead). void GetMatchingEntities(EntityQueryCondition *cond, BitArrayIntegerSet &matching_entities, std::vector> &compute_results, bool is_first, bool update_matching_entities); diff --git a/src/Amalgam/interpreter/InterpreterOpcodesBase.cpp b/src/Amalgam/interpreter/InterpreterOpcodesBase.cpp index 8c759568..34776dd7 100644 --- a/src/Amalgam/interpreter/InterpreterOpcodesBase.cpp +++ b/src/Amalgam/interpreter/InterpreterOpcodesBase.cpp @@ -311,9 +311,13 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_RECLAIM_RESOURCES(Evaluabl if(ocn.size() > 1) apply_to_all_contained_entities = InterpretNodeIntoBoolValue(ocn[1]); - bool clear_query_caches = true; + auto clear_query_caches_node = EvaluableNodeReference::Null(); + auto node_stack = CreateOpcodeStackStateSaver(); if(ocn.size() > 2) - clear_query_caches = InterpretNodeIntoBoolValue(ocn[2]); + { + clear_query_caches_node = InterpretNode(ocn[2]); + node_stack.PushEvaluableNode(clear_query_caches_node); + } bool collect_garbage = true; if(ocn.size() > 3) @@ -330,6 +334,13 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_RECLAIM_RESOURCES(Evaluabl else target_entity = EntityWriteReference(curEntity); + bool clear_all_query_caches = false; + bool clear_select_query_caches = false; + if(EvaluableNode::IsOrderedArray(clear_query_caches_node)) + clear_select_query_caches = true; + else + clear_all_query_caches = EvaluableNode::ToBool(clear_query_caches_node); + if(apply_to_all_contained_entities) { //lock all entities @@ -338,12 +349,26 @@ EvaluableNodeReference Interpreter::InterpretNode_ENT_RECLAIM_RESOURCES(Evaluabl return EvaluableNodeReference::Null(); for(auto &e : *contained_entities) - e->ReclaimResources(clear_query_caches, collect_garbage, force_free_memory); + { + e->ReclaimResources(clear_all_query_caches, collect_garbage, force_free_memory); + if(clear_select_query_caches) + { + for(auto cn : clear_query_caches_node->GetOrderedChildNodesReference()) + target_entity->ClearQueryCacheForLabel(EvaluableNode::ToStringIDIfExists(cn)); + } + } } else { - target_entity->ReclaimResources(clear_query_caches, collect_garbage, force_free_memory); - } + target_entity->ReclaimResources(clear_all_query_caches, collect_garbage, force_free_memory); + if(clear_select_query_caches) + { + for(auto cn : clear_query_caches_node->GetOrderedChildNodesReference()) + target_entity->ClearQueryCacheForLabel(EvaluableNode::ToStringIDIfExists(cn)); + } + } + + evaluableNodeManager->FreeNodeTreeIfPossible(clear_query_caches_node); return EvaluableNodeReference::Null(); }