Decouple clearing condition cache from evaluating general messages

This commit is contained in:
Oliver Hamlet
2026-01-02 10:15:39 +00:00
parent 364e2d25c0
commit 46c3ddc884
9 changed files with 121 additions and 41 deletions
+12
View File
@@ -120,6 +120,18 @@ public:
*/
virtual bool Evaluate(const std::string& condition) const = 0;
/**
* @brief Clears the cache of metadata condition evaluation results.
* @details As many conditions involve reading files and/or directories,
* libloot caches the results of condition evaluation and reuses
* those cached results in subsequent evaluations.
*
* Clearing the condition cache means that the next time a condition
* is evaluated, it will be evaluated from scratch instead of using a
* cached result.
*/
virtual void ClearConditionCache() = 0;
/**
* @}
* @name Non-plugin Data Access
+8
View File
@@ -52,6 +52,14 @@ bool Database::Evaluate(const std::string& condition) const {
}
}
void Database::ClearConditionCache() {
try {
return database_->clear_condition_cache();
} catch (const ::rust::Error& e) {
std::rethrow_exception(mapError(e));
}
}
std::vector<std::string> Database::GetKnownBashTags() const {
try {
return convert<std::string>(database_->known_bash_tags());
+2
View File
@@ -26,6 +26,8 @@ public:
bool Evaluate(const std::string& condition) const override;
void ClearConditionCache() override;
std::vector<std::string> GetKnownBashTags() const override;
std::vector<Message> GetGeneralMessages(
+9
View File
@@ -94,6 +94,15 @@ impl Database {
.map_err(Into::into)
}
pub fn clear_condition_cache(&self) -> Result<(), VerboseError> {
self.0
.write()
.map_err(DatabaseLockPoisonError::from)?
.clear_condition_cache();
Ok(())
}
pub fn known_bash_tags(&self) -> Result<Vec<String>, VerboseError> {
Ok(self
.0
+2
View File
@@ -266,6 +266,8 @@ mod ffi {
pub fn evaluate(&self, condition: &str) -> Result<bool>;
pub fn clear_condition_cache(&self) -> Result<()>;
pub fn known_bash_tags(&self) -> Result<Vec<String>>;
pub fn general_messages(&self, evaluate_conditions: bool) -> Result<Vec<Message>>;
@@ -277,6 +277,21 @@ TEST_P(DatabaseInterfaceTest, evaluateShouldReturnFalseIfTheConditionIsFalse) {
EXPECT_FALSE(handle_->GetDatabase().Evaluate("file(\"missing.esp\")"));
}
TEST_P(DatabaseInterfaceTest,
clearConditionCacheShouldCauseConditionsToBeEvaluatedFromScratch) {
const auto condition = "file(\"Blank.esp\")";
EXPECT_TRUE(handle_->GetDatabase().Evaluate(condition));
std::filesystem::remove(dataPath / "Blank.esp");
EXPECT_TRUE(handle_->GetDatabase().Evaluate(condition));
handle_->GetDatabase().ClearConditionCache();
EXPECT_FALSE(handle_->GetDatabase().Evaluate(condition));
}
TEST_P(DatabaseInterfaceTest,
getGroupsShouldReturnAllGroupsListedInTheLoadedMetadata) {
ASSERT_NO_THROW(GenerateMasterlist());
+10
View File
@@ -128,6 +128,16 @@ impl Database {
.map_err(Into::into)
}
#[napi]
pub fn clear_condition_cache(&self) -> Result<(), VerboseError> {
self.0
.write()
.map_err(DatabaseLockPoisonError::from)?
.clear_condition_cache();
Ok(())
}
#[napi]
pub fn known_bash_tags(&self) -> Result<Vec<String>, VerboseError> {
Ok(self
+9
View File
@@ -98,6 +98,15 @@ impl Database {
.map_err(Into::into)
}
pub fn clear_condition_cache(&self) -> Result<(), VerboseError> {
self.0
.write()
.map_err(DatabaseLockPoisonError::from)?
.clear_condition_cache();
Ok(())
}
pub fn known_bash_tags(&self) -> Result<Vec<String>, VerboseError> {
Ok(self
.0
+54 -41
View File
@@ -75,13 +75,6 @@ impl Database {
&mut self.condition_evaluator_state
}
pub(crate) fn clear_condition_cache(&mut self) {
if let Err(e) = self.condition_evaluator_state.clear_condition_cache() {
logging::error!("The condition cache's lock is poisoned, assigning a new cache");
*e.into_inner() = HashMap::new();
}
}
/// Loads the masterlist from the given path.
///
/// Replaces any existing data that was previously loaded from a masterlist.
@@ -154,6 +147,22 @@ impl Database {
evaluate_condition(condition, &self.condition_evaluator_state).map_err(Into::into)
}
/// Clears the cache of metadata condition evaluation results.
///
/// As many conditions involve reading files and/or directories, libloot
/// caches the results of condition evaluation and reuses those cached
/// results in subsequent evaluations.
///
/// Clearing the condition cache means that the next time a condition is
/// evaluated, it will be evaluated from scratch instead of using a cached
/// result.
pub fn clear_condition_cache(&mut self) {
if let Err(e) = self.condition_evaluator_state.clear_condition_cache() {
logging::error!("The condition cache's lock is poisoned, assigning a new cache");
*e.into_inner() = HashMap::new();
}
}
/// Gets the Bash Tags that are listed in the loaded metadata lists.
///
/// Bash Tag suggestions can include Bash Tags not in this list.
@@ -165,17 +174,10 @@ impl Database {
}
/// Get all general messages listed in the loaded metadata lists.
///
/// Evaluating general message conditions also clears the condition cache
/// before evaluating conditions.
pub fn general_messages(
&mut self,
&self,
evaluate_conditions: EvalMode,
) -> Result<Vec<Message>, ConditionEvaluationError> {
if evaluate_conditions == EvalMode::Evaluate {
self.clear_condition_cache();
}
let messages_iter = self
.masterlist
.messages()
@@ -237,9 +239,6 @@ impl Database {
}
/// Get all of a plugin's loaded metadata.
///
/// Evaluating plugin metadata conditions does **not** clear the condition
/// cache.
pub fn plugin_metadata(
&self,
plugin_name: &str,
@@ -267,9 +266,6 @@ impl Database {
}
/// Get a plugin's metadata loaded from the given userlist.
///
/// Evaluating plugin metadata conditions does **not** clear the condition
/// cache.
pub fn plugin_user_metadata(
&self,
plugin_name: &str,
@@ -838,6 +834,43 @@ plugins:
}
}
mod evaluate {
use super::*;
#[test]
fn should_return_true_if_the_condition_is_true() {
let fixture = Fixture::new(GameType::Oblivion);
let database = fixture.database();
assert!(database.evaluate("file(\"Blank.esp\")").unwrap());
}
#[test]
fn should_return_false_if_the_condition_is_false() {
let fixture = Fixture::new(GameType::Oblivion);
let database = fixture.database();
assert!(!database.evaluate("file(\"missing.esp\")").unwrap());
}
}
#[test]
fn clear_condition_cache_should_cause_conditions_to_be_evaluated_from_scratch() {
let fixture = Fixture::new(GameType::Oblivion);
let condition = "file(\"Blank.esp\")";
let mut database = fixture.database();
assert!(database.evaluate(condition).unwrap());
std::fs::remove_file(fixture.inner.data_path().join("Blank.esp")).unwrap();
assert!(database.evaluate(condition).unwrap());
database.clear_condition_cache();
assert!(!database.evaluate(condition).unwrap());
}
#[test]
fn known_bash_tags_should_append_userlist_tags_to_masterlist_tags() {
let fixture = Fixture::new(GameType::Oblivion);
@@ -914,26 +947,6 @@ plugins:
}
}
mod evaluate {
use super::*;
#[test]
fn should_return_true_if_the_condition_is_true() {
let fixture = Fixture::new(GameType::Oblivion);
let database = fixture.database();
assert!(database.evaluate("file(\"Blank.esp\")").unwrap());
}
#[test]
fn should_return_false_if_the_condition_is_false() {
let fixture = Fixture::new(GameType::Oblivion);
let database = fixture.database();
assert!(!database.evaluate("file(\"missing.esp\")").unwrap());
}
}
mod groups {
use super::*;