use super::CompiledLogic;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use rapidhash::fast::RapidHasher;
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompiledLogicId(u64);
impl CompiledLogicId {
pub fn as_u64(&self) -> u64 {
self.0
}
pub fn from_u64(id: u64) -> Self {
Self(id)
}
}
static COMPILED_LOGIC_STORE: Lazy<CompiledLogicStore> = Lazy::new(|| {
CompiledLogicStore {
store: DashMap::new(),
id_map: DashMap::new(),
next_id: AtomicU64::new(1), }
});
#[inline]
fn hash_value(value: &serde_json::Value, hasher: &mut RapidHasher) {
match value {
serde_json::Value::Null => 0u8.hash(hasher),
serde_json::Value::Bool(b) => {
1u8.hash(hasher);
b.hash(hasher);
}
serde_json::Value::Number(n) => {
2u8.hash(hasher);
n.as_f64().unwrap_or(0.0).to_bits().hash(hasher);
}
serde_json::Value::String(s) => {
3u8.hash(hasher);
s.hash(hasher);
}
serde_json::Value::Array(arr) => {
4u8.hash(hasher);
arr.len().hash(hasher);
for item in arr {
hash_value(item, hasher);
}
}
serde_json::Value::Object(obj) => {
5u8.hash(hasher);
obj.len().hash(hasher);
for (k, v) in obj {
k.hash(hasher);
hash_value(v, hasher);
}
}
}
}
struct CompiledLogicStore {
store: DashMap<u64, (CompiledLogicId, CompiledLogic)>,
id_map: DashMap<u64, CompiledLogic>,
next_id: AtomicU64,
}
impl CompiledLogicStore {
fn compile_value(&self, logic: &serde_json::Value) -> Result<CompiledLogicId, String> {
let mut hasher = RapidHasher::default();
hash_value(logic, &mut hasher);
let hash = hasher.finish();
if let Some(entry) = self.store.get(&hash) {
return Ok(entry.0);
}
let compiled = CompiledLogic::compile(logic)?;
match self.store.entry(hash) {
dashmap::mapref::entry::Entry::Occupied(o) => {
Ok(o.get().0)
}
dashmap::mapref::entry::Entry::Vacant(v) => {
let id = CompiledLogicId(self.next_id.fetch_add(1, Ordering::SeqCst));
self.id_map.insert(id.0, compiled.clone());
v.insert((id, compiled));
Ok(id)
}
}
}
fn compile(&self, logic_json: &str) -> Result<CompiledLogicId, String> {
let logic: serde_json::Value = serde_json::from_str(logic_json)
.map_err(|e| format!("Failed to parse logic JSON: {}", e))?;
self.compile_value(&logic)
}
fn get(&self, id: CompiledLogicId) -> Option<CompiledLogic> {
self.id_map.get(&id.0).map(|v| v.clone())
}
fn stats(&self) -> CompiledLogicStoreStats {
CompiledLogicStoreStats {
compiled_count: self.store.len(),
next_id: self.next_id.load(Ordering::SeqCst),
}
}
#[allow(dead_code)]
fn clear(&self) {
self.store.clear();
self.id_map.clear();
self.next_id.store(1, Ordering::SeqCst);
}
}
#[derive(Debug, Clone)]
pub struct CompiledLogicStoreStats {
pub compiled_count: usize,
pub next_id: u64,
}
pub fn compile_logic(logic_json: &str) -> Result<CompiledLogicId, String> {
COMPILED_LOGIC_STORE.compile(logic_json)
}
pub fn compile_logic_value(logic: &serde_json::Value) -> Result<CompiledLogicId, String> {
COMPILED_LOGIC_STORE.compile_value(logic)
}
pub fn get_compiled_logic(id: CompiledLogicId) -> Option<CompiledLogic> {
COMPILED_LOGIC_STORE.get(id)
}
pub fn get_store_stats() -> CompiledLogicStoreStats {
COMPILED_LOGIC_STORE.stats()
}
#[cfg(test)]
pub fn clear_store() {
COMPILED_LOGIC_STORE.clear()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_and_get() {
let logic = r#"{"==": [{"var": "x"}, 10]}"#;
let id = compile_logic(logic).expect("Failed to compile");
let compiled = get_compiled_logic(id);
assert!(compiled.is_some());
}
#[test]
fn test_deduplication() {
let logic = r#"{"*": [{"var": "a"}, 2]}"#;
let id1 = compile_logic(logic).expect("Failed to compile");
let id2 = compile_logic(logic).expect("Failed to compile");
assert_eq!(id1, id2);
}
#[test]
fn test_different_logic() {
let logic1 = r#"{"*": [{"var": "a"}, 2]}"#;
let logic2 = r#"{"*": [{"var": "b"}, 3]}"#;
let id1 = compile_logic(logic1).expect("Failed to compile");
let id2 = compile_logic(logic2).expect("Failed to compile");
assert_ne!(id1, id2);
}
#[test]
fn test_stats() {
let stats_before = get_store_stats();
let logic = r#"{"+": [1, 2, 3]}"#;
let _ = compile_logic(logic).expect("Failed to compile");
let stats_after = get_store_stats();
assert!(stats_after.compiled_count >= stats_before.compiled_count);
assert!(stats_after.next_id >= stats_before.next_id);
}
}