khora_editor/
hot_reload.rs1use std::collections::HashSet;
17use std::sync::{Arc, Mutex};
18
19use khora_sdk::editor_ui::AssetEntry;
20use khora_sdk::AssetChangeKind;
21use khora_sdk::EditorState;
22
23use crate::project_vfs::ProjectVfs;
24
25pub fn pump(pvfs_mutex: &Arc<Mutex<ProjectVfs>>, editor_state: &Arc<Mutex<EditorState>>) {
29 let Ok(mut pvfs) = pvfs_mutex.lock() else {
30 return;
31 };
32 let events = pvfs.poll_changes();
33 if events.is_empty() {
34 return;
35 }
36
37 let mut needs_reindex = false;
40 let mut modified: HashSet<String> = HashSet::new();
41 for e in events {
42 match e.kind {
43 AssetChangeKind::Modified => {
44 modified.insert(e.rel_path);
45 }
46 AssetChangeKind::Created | AssetChangeKind::Removed => {
47 log::info!(
48 "Hot reload: {:?} '{}' — full reindex queued",
49 e.kind,
50 e.rel_path
51 );
52 needs_reindex = true;
53 }
54 }
55 }
56
57 for rel in &modified {
61 let uuid = pvfs.resolve_uuid(rel);
62 let dropped = pvfs.asset_service.invalidate(&uuid);
63 log::info!(
64 "Hot reload: Modified '{}' (cache dropped: {})",
65 rel,
66 dropped
67 );
68 }
69
70 if !needs_reindex {
71 return;
72 }
73 if let Err(e) = pvfs.rebuild_index() {
74 log::error!("Hot reload: failed to rebuild index: {:#}", e);
75 return;
76 }
77 let entries = collect_asset_entries(&pvfs);
78 let dirs = pvfs.list_dirs();
79 if let Ok(mut state) = editor_state.lock() {
80 state.asset_entries = entries;
81 state.asset_dirs = dirs;
82 state.asset_epoch = state.asset_epoch.wrapping_add(1);
83 }
84}
85
86pub fn collect_asset_entries(pvfs: &ProjectVfs) -> Vec<AssetEntry> {
90 pvfs.asset_service
91 .vfs()
92 .iter_all()
93 .map(|m| {
94 let rel_str = m.source_path.to_string_lossy().to_string();
95 let name = m
96 .source_path
97 .file_name()
98 .map(|n| n.to_string_lossy().to_string())
99 .unwrap_or_else(|| rel_str.clone());
100 AssetEntry {
101 name,
102 asset_type: m.asset_type_name.clone(),
103 source_path: rel_str,
104 }
105 })
106 .collect()
107}