Optimizations

* New Features

Added series information display with episodes, cast, and metadata in playlist explorer
Introduced multi-cluster playlist endpoints (live, VOD, series) for flexible content retrieval
Added series info lookup and filtering capabilities
Implemented sidebar navigation for source editor with dedicated pages (Main, Options, Staged, Advanced, Alias)
Added filter state controls (All, Selected, Deselected) for playlist category management
Enabled Trakt configuration toggle to control integration
Performance Improvements

Optimized B+Tree indexing for faster queries
Reduced startup latency with disk-based operations and optional memory caching
Improved string memory handling through efficient reference-counted strings
UI Enhancements

Enhanced playlist explorer with series details and episode grouping by season
Added new UI icons (Options, Advanced, Staging, Alias, Trakt)
Improved form layouts with toolbar controls
Enhanced radio button group styling for better visual hierarchy

* New Features

Added series information display with episodes, cast, and metadata in playlist explorer
Introduced multi-cluster playlist endpoints (live, VOD, series) for flexible content retrieval
Added series info lookup and filtering capabilities
Implemented sidebar navigation for source editor with dedicated pages (Main, Options, Staged, Advanced, Alias)
Added filter state controls (All, Selected, Deselected) for playlist category management
Enabled Trakt configuration toggle to control integration
Performance Improvements

Optimized B+Tree indexing for faster queries
Reduced startup latency with disk-based operations and optional memory caching
Improved string memory handling through efficient reference-counted strings
UI Enhancements

Enhanced playlist explorer with series details and episode grouping by season
Added new UI icons (Options, Advanced, Staging, Alias, Trakt)
Improved form layouts with toolbar controls
Enhanced radio button group styling for better visual hierarchy

* New Features

Added series information display with episodes, cast, and metadata in playlist explorer
Introduced multi-cluster playlist endpoints (live, VOD, series) for flexible content retrieval
Added series info lookup and filtering capabilities
Implemented sidebar navigation for source editor with dedicated pages (Main, Options, Staged, Advanced, Alias)
Added filter state controls (All, Selected, Deselected) for playlist category management
Enabled Trakt configuration toggle to control integration
Performance Improvements

Optimized B+Tree indexing for faster queries
Reduced startup latency with disk-based operations and optional memory caching
Improved string memory handling through efficient reference-counted strings
UI Enhancements

Enhanced playlist explorer with series details and episode grouping by season
Added new UI icons (Options, Advanced, Staging, Alias, Trakt)
Improved form layouts with toolbar controls
Enhanced radio button group styling for better visual hierarchy

* New Features

Added series information display with episodes, cast, and metadata in playlist explorer
Introduced multi-cluster playlist endpoints (live, VOD, series) for flexible content retrieval
Added series info lookup and filtering capabilities
Implemented sidebar navigation for source editor with dedicated pages (Main, Options, Staged, Advanced, Alias)
Added filter state controls (All, Selected, Deselected) for playlist category management
Enabled Trakt configuration toggle to control integration
Performance Improvements

Optimized B+Tree indexing for faster queries
Reduced startup latency with disk-based operations and optional memory caching
Improved string memory handling through efficient reference-counted strings
UI Enhancements

Enhanced playlist explorer with series details and episode grouping by season
Added new UI icons (Options, Advanced, Staging, Alias, Trakt)
Improved form layouts with toolbar controls
Enhanced radio button group styling for better visual hierarchy
This commit is contained in:
euzu
2026-01-20 10:09:58 +01:00
committed by GitHub
parent 39c4fca9ca
commit 16fcd7f882
139 changed files with 5094 additions and 2797 deletions
+243 -43
View File
@@ -1,68 +1,145 @@
//! String interning utilities for memory optimization.
//!
//! Provides a scoped string interner to deduplicate frequently repeated
//! Provides a global string interner to deduplicate frequently repeated
//! strings like `input_name` and `group` in playlist items.
use crate::model::UUIDType;
use serde::{Deserialize, Deserializer, Serializer};
use std::borrow::Cow;
use std::collections::HashSet;
use std::sync::Arc;
use std::sync::{Arc, LazyLock, RwLock};
/// A scoped string interner that deduplicates strings within its lifetime.
/// Dropping this struct releases all internal references.
#[derive(Default)]
pub struct StringInterner {
pool: HashSet<Arc<str>>,
// Global interner store
static INTERNER: LazyLock<RwLock<HashSet<Arc<str>>>> = LazyLock::new(|| {
RwLock::new(HashSet::new())
});
pub trait Internable {
fn intern(self) -> Arc<str>;
}
impl StringInterner {
/// Creates a new empty interner.
pub fn new() -> Self {
Self {
pool: HashSet::new(),
impl Internable for &Arc<str> {
fn intern(self) -> Arc<str> {
Arc::clone(self)
}
}
impl Internable for &Cow<'_, str> {
fn intern(self) -> Arc<str> {
match self {
Cow::Borrowed(s) => intern_str(s),
Cow::Owned(s) => intern_string(s.clone()),
}
}
}
/// Interns a string slice.
pub fn intern(&mut self, s: &str) -> Arc<str> {
if let Some(existing) = self.pool.get(s) {
Arc::clone(existing)
} else {
let arc: Arc<str> = s.into();
self.pool.insert(Arc::clone(&arc));
arc
}
impl Internable for &UUIDType {
fn intern(self) -> Arc<str> {
intern_string(self.to_string())
}
}
/// Interns an owned string.
pub fn intern_string(&mut self, s: String) -> Arc<str> {
if let Some(existing) = self.pool.get(s.as_str()) {
impl Internable for String {
fn intern(self) -> Arc<str> {
intern_string(self)
}
}
impl Internable for &String {
fn intern(self) -> Arc<str> {
intern_str(self.as_str())
}
}
impl Internable for &str {
fn intern(self) -> Arc<str> {
intern_str(self)
}
}
impl Internable for u32 {
fn intern(self) -> Arc<str> {
intern_string(self.to_string())
}
}
impl Internable for u64 {
fn intern(self) -> Arc<str> {
intern_string(self.to_string())
}
}
impl Internable for i64 {
fn intern(self) -> Arc<str> {
intern_string(self.to_string())
}
}
/// Interns a string slice.
fn intern_str(s: &str) -> Arc<str> {
// Try read first
if let Ok(guard) = INTERNER.read() {
if let Some(existing) = guard.get(s) {
return Arc::clone(existing);
}
// Convert String to Arc<str> directly
let arc: Arc<str> = Arc::from(s);
self.pool.insert(Arc::clone(&arc));
arc
drop(guard);
}
// Write lock
if let Ok(mut guard) = INTERNER.write() {
// Double check
if let Some(existing) = guard.get(s) {
return Arc::clone(existing);
}
let arc: Arc<str> = Arc::from(s);
guard.insert(Arc::clone(&arc));
return arc;
}
Arc::from(s)
}
/// Legacy wrapper: creates a new Arc<str> without interning.
/// Safe fallback to prevent memory leaks from global/thread-local storage.
#[inline]
pub fn intern(s: &str) -> Arc<str> {
s.into()
/// Interns an owned string.
fn intern_string(s: String) -> Arc<str> {
// Try read first
if let Ok(guard) = INTERNER.read() {
if let Some(existing) = guard.get(s.as_str()) {
return Arc::clone(existing);
}
drop(guard);
}
// Write lock
if let Ok(mut guard) = INTERNER.write() {
// Double check
if let Some(existing) = guard.get(s.as_str()) {
return Arc::clone(existing);
}
let arc: Arc<str> = Arc::from(s);
guard.insert(Arc::clone(&arc));
return arc;
}
Arc::from(s)
}
/// Legacy wrapper: creates a new Arc<str> without interning.
#[inline]
pub fn intern_string(s: String) -> Arc<str> {
s.into()
/// Garbage collection: removes strings that are only referenced by the cache.
pub fn interner_gc() -> usize {
if let Ok(mut guard) = INTERNER.write() {
let before = guard.len();
// Arc::strong_count == 1 means the cache is the only one holding it.
guard.retain(|s| Arc::strong_count(s) > 1);
let removed = before - guard.len();
if removed > 0 {
log::debug!("Pruned {removed} unused interned strings ({} remaining)", guard.len());
}
return removed;
}
0
}
/// Serde support for `Arc<str>` fields.
/// Note: This does NOT deduplicate on load to avoid global state leaks.
pub mod arc_str_serde {
use serde::{Deserialize, Deserializer, Serializer};
use std::sync::Arc;
use super::*;
pub fn serialize<S>(value: &Arc<str>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -75,7 +152,130 @@ pub mod arc_str_serde {
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(s.into())
Ok(s.intern())
}
}
pub mod arc_str_vec_serde {
use super::*;
use serde::ser::SerializeSeq;
pub fn serialize<S>(
value: &Vec<Arc<str>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(value.len()))?;
for s in value {
seq.serialize_element(s.as_ref())?;
}
seq.end()
}
pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<Vec<Arc<str>>, D::Error>
where
D: Deserializer<'de>,
{
let vec = Vec::<String>::deserialize(deserializer)?;
Ok(vec.into_iter().map(|s| s.intern()).collect())
}
}
pub mod arc_str_serde_none {
use super::*;
pub fn serialize<S>(value: &Arc<str>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(value)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(s.intern())
}
}
pub mod arc_str_option_serde {
use super::*;
pub fn serialize<S>(value: &Option<Arc<str>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(s) => serializer.serialize_str(s),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Arc<str>>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
Ok(opt.map(|s| s.intern()))
}
pub fn serialize_null_if_empty<S>(value: &Option<Arc<str>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
None => serializer.serialize_none(),
Some(s) if s.is_empty() => serializer.serialize_none(),
Some(s) => serializer.serialize_str(s),
}
}
}
pub mod arc_str_option_serde_none {
use super::*;
pub fn serialize<S>(value: &Option<Arc<str>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(s) => serializer.serialize_str(s),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Arc<str>>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
Ok(opt.map(|s| s.intern()))
}
}
pub fn arc_str_default_on_null<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
Ok(opt.unwrap_or_default().intern())
}
pub use arc_str_default_on_null as arc_str_none_default_on_null;
pub fn deserialize_as_option_arc_str<'de, D>(deserializer: D) -> Result<Option<Arc<str>>, D::Error>
where
D: Deserializer<'de>,
{
let value: serde_json::Value = Deserialize::deserialize(deserializer)?;
match value {
serde_json::Value::String(s) => Ok(Some(s.intern())),
serde_json::Value::Number(s) => Ok(Some(s.to_string().intern())),
_ => Ok(None),
}
}
pub use deserialize_as_option_arc_str as deserialize_as_option_arc_str_none;