artistpath_core/pathfinding/
utils.rs1use crate::pathfinding_config::PathfindingConfig;
2use byteorder::{LittleEndian, ReadBytesExt};
3use memmap2::Mmap;
4use rustc_hash::FxHashMap;
5use std::{
6 fs::File,
7 io::{Cursor, Read},
8 path::Path,
9};
10use uuid::Uuid;
11
12pub type PathStep = (Uuid, f32);
13pub type PathResult = (Option<Vec<PathStep>>, usize, f64);
14
15#[derive(Debug, Clone)]
16pub enum EnhancedPathResult {
17 Success {
18 primary_path: Vec<PathStep>,
19 related_artists: FxHashMap<Uuid, (f32, usize)>,
20 connections: FxHashMap<Uuid, Vec<(Uuid, f32)>>,
21 artists_visited: usize,
22 duration_ms: u64,
23 },
24 PathTooLong {
25 primary_path: Vec<PathStep>,
26 path_length: usize,
27 minimum_budget_needed: usize,
28 artists_visited: usize,
29 duration_ms: u64,
30 },
31 NoPath {
32 artists_visited: usize,
33 duration_ms: u64,
34 },
35}
36
37pub fn open_memory_mapped_file(file_path: &Path) -> Result<Mmap, std::io::Error> {
38 let file = File::open(file_path)?;
39 unsafe { Mmap::map(&file) }
40}
41
42pub fn get_artist_connections(
43 artist_id: Uuid,
44 graph_data: &Mmap,
45 graph_index: &FxHashMap<Uuid, u64>,
46 config: &PathfindingConfig,
47) -> Vec<(Uuid, f32)> {
48 let file_position = match find_artist_position(artist_id, graph_index) {
49 Some(pos) => pos,
50 None => return vec![],
51 };
52
53 if file_position >= graph_data.len() {
54 return vec![];
55 }
56
57 let artist_data_cursor = create_cursor_at_position(graph_data, file_position);
58
59 match parse_artist_connections(artist_id, artist_data_cursor) {
60 Ok(raw_connections) => filter_and_sort_connections(raw_connections, config),
61 Err(_) => vec![],
62 }
63}
64
65fn find_artist_position(artist_id: Uuid, graph_index: &FxHashMap<Uuid, u64>) -> Option<usize> {
66 graph_index.get(&artist_id).map(|&pos| pos as usize)
67}
68
69fn create_cursor_at_position(graph_data: &Mmap, position: usize) -> Cursor<&[u8]> {
70 Cursor::new(&graph_data[position..])
71}
72
73fn parse_artist_connections(expected_artist_id: Uuid, mut cursor: Cursor<&[u8]>) -> Result<Vec<(Uuid, f32)>, std::io::Error> {
74 let stored_artist_id = read_uuid_from_cursor(&mut cursor)?;
75
76 if stored_artist_id != expected_artist_id {
77 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Artist ID mismatch"));
78 }
79
80 let connection_count = cursor.read_u32::<LittleEndian>()? as usize;
81 let mut connections = Vec::with_capacity(connection_count);
82
83 for _ in 0..connection_count {
84 let connected_artist_id = read_uuid_from_cursor(&mut cursor)?;
85 let similarity_score = cursor.read_f32::<LittleEndian>()?;
86 connections.push((connected_artist_id, similarity_score));
87 }
88
89 Ok(connections)
90}
91
92fn read_uuid_from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Uuid, std::io::Error> {
93 let mut uuid_bytes = [0u8; 16];
94 cursor.read_exact(&mut uuid_bytes)?;
95 Ok(Uuid::from_bytes(uuid_bytes))
96}
97
98fn filter_and_sort_connections(mut connections: Vec<(Uuid, f32)>, config: &PathfindingConfig) -> Vec<(Uuid, f32)> {
99 if config.min_match > 0.0 {
100 connections.retain(|(_, similarity)| *similarity >= config.min_match);
101 }
102
103 connections.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
104 connections.truncate(config.top_related);
105
106 connections
107}
108
109pub fn reconstruct_path(
110 parent_map: &FxHashMap<Uuid, (Uuid, f32)>,
111 start: Uuid,
112 target: Uuid,
113) -> Vec<PathStep> {
114 let mut path = Vec::new();
115 let mut current_node = target;
116
117 while current_node != start {
118 let (parent_node, similarity) = parent_map[¤t_node];
119 path.push((current_node, similarity));
120 current_node = parent_node;
121 }
122
123 path.push((start, 0.0));
124 path.reverse();
125 path
126}