From 588142dc9d970cba9194f7034a04da1c9897fa3d Mon Sep 17 00:00:00 2001 From: "Christopher J. Hazard, PhD" <143410553+howsohazard@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:38:44 -0500 Subject: [PATCH 1/2] 24960: Begins improving granularity for reclaim_resources --- src/Amalgam/SeparableBoxFilterDataStore.h | 8 +++++ src/Amalgam/entity/Entity.h | 44 +++++++++++++++-------- src/Amalgam/entity/EntityQueryCaches.h | 6 ++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/Amalgam/SeparableBoxFilterDataStore.h b/src/Amalgam/SeparableBoxFilterDataStore.h index 7ba572b75..f7924d68e 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/entity/Entity.h b/src/Amalgam/entity/Entity.h index f9b489002..30ce9366e 100644 --- a/src/Amalgam/entity/Entity.h +++ b/src/Amalgam/entity/Entity.h @@ -586,18 +586,34 @@ 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; + + #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->RemoveLabelFromCache(label_sid); } //creates a cache if it does not exist @@ -607,9 +623,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 diff --git a/src/Amalgam/entity/EntityQueryCaches.h b/src/Amalgam/entity/EntityQueryCaches.h index ecef6050a..1dff79e6e 100644 --- a/src/Amalgam/entity/EntityQueryCaches.h +++ b/src/Amalgam/entity/EntityQueryCaches.h @@ -101,6 +101,12 @@ class EntityQueryCaches void EnsureLabelsAreCached(EntityQueryCondition *cond); #endif + //removes label_sid from the cache + inline void RemoveLabelFromCache(StringInternPool::StringID label_sid) + { + 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); From dd74f993e7bc46d4cdb990d52d7557374c7f3f03 Mon Sep 17 00:00:00 2001 From: "Christopher J. Hazard, PhD" <143410553+howsohazard@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:09:00 -0500 Subject: [PATCH 2/2] 24960: Finishes reclaim_resources improvements and fixes a bug where it was not working --- docs/language.js | 4 +-- src/Amalgam/amlg_code/full_test.amlg | 2 ++ src/Amalgam/entity/Entity.h | 27 +++++++------- src/Amalgam/entity/EntityQueryCaches.h | 3 ++ .../interpreter/InterpreterOpcodesBase.cpp | 35 ++++++++++++++++--- 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/docs/language.js b/docs/language.js index 3214efcd1..7aa064b57 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/amlg_code/full_test.amlg b/src/Amalgam/amlg_code/full_test.amlg index 199fd0602..43026b6aa 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 30ce9366e..a64e92eae 100644 --- a/src/Amalgam/entity/Entity.h +++ b/src/Amalgam/entity/Entity.h @@ -605,14 +605,6 @@ class Entity 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->RemoveLabelFromCache(label_sid); } @@ -761,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++; @@ -981,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) { @@ -994,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 1dff79e6e..ac493c883 100644 --- a/src/Amalgam/entity/EntityQueryCaches.h +++ b/src/Amalgam/entity/EntityQueryCaches.h @@ -104,6 +104,9 @@ class EntityQueryCaches //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); } diff --git a/src/Amalgam/interpreter/InterpreterOpcodesBase.cpp b/src/Amalgam/interpreter/InterpreterOpcodesBase.cpp index 8c7595688..34776dd7d 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(); }