wgtk/res/
mod.rs

1//! Game's resources fetching and indexing.
2
3pub mod package;
4
5use std::io::{Read, Seek, SeekFrom};
6use std::collections::BTreeMap;
7use std::fs::{File, ReadDir};
8use std::sync::{Arc, Mutex};
9use std::path::PathBuf;
10use std::{fs, io};
11
12use indexmap::IndexMap;
13
14use package::{PackageReader, PackageFileReader};
15
16
17/// Name of the directory storing packages in the "res/" directory.
18const PACKAGES_DIR_NAME: &'static str = "packages";
19
20
21/// A virtual read-only filesystem you can use to walk through the game's resources. This
22/// filesystem is designed to work really fast on systems where it will run for a long
23/// time and take advantage of its internal cache, but it will also work on run-once
24/// environment such as CLI where the first answer latency must be minimal.
25/// 
26/// This filesystem is made to be shared between threads and immutably accessed.
27/// 
28/// Internally, this filesystem has a cache to improve response delay. The challenge is
29/// that directories may reside in many packages, but files are present only in one
30/// package.
31#[derive(Debug, Clone)]
32pub struct ResFilesystem {
33    /// Shared part of the filesystem, used when returning independent handles like
34    /// read dir iterator.
35    shared: Arc<Shared>,
36}
37
38/// Immutable shared data 
39#[derive(Debug)]
40struct Shared {
41    /// Path to the "res/" directory.
42    dir_path: PathBuf,
43    /// Mutable part of the shared data, behind mutex.
44    mutable: Mutex<SharedMut>,
45}
46
47/// Mutex shared part of the resource filesystem.
48#[derive(Debug)]
49struct SharedMut {
50    /// Pending packages to be opened and cached.
51    pending_package_path: Vec<PathBuf>,
52    /// Cache for opened package files.
53    package_reader_cache: IndexMap<PathBuf, PackageReader<File>>,
54    /// Package open errors are silently ignored when reading files and directories, so
55    /// this vector contains the errors that may happen and can later be retrieved.
56    package_open_errors: Vec<(PathBuf, io::Error)>,
57    /// Cache for known files and directories.
58    node_cache: NodeCache,
59}
60
61impl ResFilesystem {
62
63    /// Create a new resources filesystem with the given options. This function is
64    /// blocking while it is doing a rudimentary early indexing, so this may take some
65    /// time.
66    pub fn new<P: Into<PathBuf>>(dir_path: P) -> io::Result<Self> {
67
68        let dir_path = dir_path.into();
69        let mut pending_package_cache = Vec::new();
70
71        for entry in fs::read_dir(dir_path.join(PACKAGES_DIR_NAME))? {
72            
73            let entry = entry?;
74            let entry_type = entry.file_type()?;
75            if !entry_type.is_file() {
76                continue;
77            }
78
79            if !entry.file_name().as_encoded_bytes().ends_with(b".pkg") {
80                continue;
81            }
82
83            pending_package_cache.push(entry.path());
84
85        }
86
87        Ok(Self { 
88            shared: Arc::new(Shared {
89                dir_path,
90                mutable: Mutex::new(SharedMut {
91                    pending_package_path: pending_package_cache,
92                    package_reader_cache: IndexMap::new(),
93                    package_open_errors: Vec::new(),
94                    node_cache: NodeCache::new(),
95                }),
96            }),
97        })
98
99    }
100
101    /// Read a file from its path in the resource filesystem.
102    pub fn read<P: AsRef<str>>(&self, file_path: P) -> io::Result<ResReadFile> {
103
104        let file_path = file_path.as_ref();
105        if file_path.starts_with('/') {
106            return Err(io::ErrorKind::NotFound.into());
107        }
108
109        let native_file_path = self.shared.dir_path.join(file_path);
110        if native_file_path.is_file() {
111            match File::open(native_file_path) {
112                Ok(file) => return Ok(ResReadFile(ReadFileInner::Native(file))),
113                Err(_) => (), // For now we skip this.
114            }
115        }
116
117        self.shared.mutable.lock().unwrap()
118            .read(file_path)
119            .map(|reader| ResReadFile(ReadFileInner::Package(reader)))
120
121    }
122
123    /// Read a directory's entries in the resource filesystem. This function may be 
124    /// blocking a short time because it needs to find the first node of that directory.
125    /// 
126    /// This function may return a file not found error if no package contains this 
127    /// directory.
128    pub fn read_dir<P: AsRef<str>>(&self, dir_path: P) -> io::Result<ResReadDir> {
129
130        // Instant error if leading separator.
131        let dir_path = dir_path.as_ref();
132        if dir_path.starts_with('/') {
133            return Err(io::ErrorKind::NotFound.into());
134        }
135
136        // Remove an possible trailing separator.
137        let dir_path = dir_path.strip_suffix('/').unwrap_or(dir_path);
138
139        let native_dir_path = self.shared.dir_path.join(dir_path);
140        let native_read_dir = fs::read_dir(native_dir_path).ok();
141        
142        let mut mutable = self.shared.mutable.lock().unwrap();
143        let mut dir_index = None;
144
145        // Initially we want to know the cache node index, if not found we try to open
146        // and index the next pending package.
147        while dir_index.is_none() {
148            if let Some((find_dir_index, _)) = mutable.node_cache.find_dir(dir_path) {
149                dir_index = Some(find_dir_index);
150            } else if !mutable.try_open_pending_package() {
151                // No package contains this directory, only error if native read dir 
152                // also returned an error.
153                if native_read_dir.is_none() {
154                    return Err(io::ErrorKind::NotFound.into()); 
155                } else {
156                    break;
157                }
158            }
159        }
160
161        Ok(ResReadDir {
162            dir_path: Arc::from(dir_path),
163            native_read_dir,
164            package_read_dir: dir_index.map(|dir_index| PackageReadDir {
165                shared: Arc::clone(&self.shared),
166                dir_index,
167                remaining_names: Vec::new(),
168                last_children_count: 0,
169                last_children_last_node_index: 0,
170            }),
171        })
172    }
173
174}
175
176impl SharedMut {
177
178    fn try_read(&mut self, file_path: &str) -> io::Result<Option<PackageFileReader<File>>> {
179        
180        if let Some((_, file_info)) = self.node_cache.find_file(file_path) {
181            
182            let (
183                package_path, 
184                package_reader,
185            ) = self.package_reader_cache.get_index_mut(file_info.package_index).unwrap();
186            let mut file_reader = package_reader.read_by_index(file_info.file_index)?;
187
188            // Now that we have the reader, we want to make it owned, to do that we clone
189            // it with a new handle to the underlying package file.
190            return file_reader.try_clone_with(File::open(package_path)?).map(Some);
191
192        } else {
193            Ok(None)
194        }
195
196    }
197
198    /// Open the next pending package and index it into the cache. This returns true if a
199    /// pending package have been opened and cached, false if there are no more package.
200    /// 
201    /// An error is returned if the package could not be opened, this error is not 
202    /// critical in itself but the pending package will never be opened again.
203    /// 
204    /// Errors considered critical are ones that happen on already opened packages.
205    fn try_open_pending_package(&mut self) -> bool {
206
207        while let Some(package_path) = self.pending_package_path.pop() {
208
209            let package_file = match File::open(&package_path) {
210                Ok(file) => file,
211                Err(e) => {
212                    self.package_open_errors.push((package_path, e));
213                    continue;
214                }
215            };
216
217            let package_reader = match PackageReader::new(package_file) {
218                Ok(reader) => reader,
219                Err(e) => {
220                    self.package_open_errors.push((package_path, e));
221                    continue;
222                }
223            };
224
225            let (
226                package_index, 
227                prev_package,
228            ) = self.package_reader_cache.insert_full(package_path, package_reader);
229            debug_assert!(prev_package.is_none(), "duplicate package reader");
230            
231            self.node_cache.index_package(package_index, &self.package_reader_cache[package_index]);
232            // println!("  cache size: {}", self.node_cache.nodes.len());
233            // println!("  dir count: {}", self.node_cache.dir_count);
234            // println!("  dir children max count: {}", self.node_cache.dir_children_max_count);
235            // println!("  node name max len: {}", self.node_cache.node_name_max_len);
236
237            return true;
238
239
240        }
241
242        false
243
244    }
245
246    /// See [`ResFilesystem::read()`].
247    fn read(&mut self, file_path: &str) -> io::Result<PackageFileReader<File>> {
248
249        if let Some(file_reader) = self.try_read(file_path)? {
250            return Ok(file_reader);
251        }
252
253        // If not found in cache, try opening more packages until we find it.
254        while self.try_open_pending_package() {
255            if let Some(file_reader) = self.try_read(file_path)? {
256                return Ok(file_reader);
257            }
258        }
259
260        Err(io::ErrorKind::NotFound.into())
261
262    }
263
264}
265
266
267/// A handle to reading a resource file, this abstraction hides the underlying file but
268/// it can be either a package file or a native file.
269#[derive(Debug)]
270pub struct ResReadFile(ReadFileInner);
271
272/// Inner handle to
273#[derive(Debug)]
274enum ReadFileInner {
275    Package(PackageFileReader<File>),
276    Native(File),
277}
278
279impl Read for ResReadFile {
280
281    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
282        match &mut self.0 {
283            ReadFileInner::Package(package) => package.read(buf),
284            ReadFileInner::Native(file) => file.read(buf),
285        }
286    }
287
288    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
289        match &mut self.0 {
290            ReadFileInner::Package(package) => package.read_exact(buf),
291            ReadFileInner::Native(file) => file.read_exact(buf),
292        }
293    }
294
295}
296
297impl Seek for ResReadFile {
298
299    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
300        match &mut self.0 {
301            ReadFileInner::Package(package) => package.seek(pos),
302            ReadFileInner::Native(file) => file.seek(pos),
303        }
304    }
305
306    fn stream_position(&mut self) -> io::Result<u64> {
307        match &mut self.0 {
308            ReadFileInner::Package(package) => package.stream_position(),
309            ReadFileInner::Native(file) => file.stream_position(),
310        }
311    }
312
313}
314
315
316/// A directory read iterator that lazily open packages as iteration advance.
317/// 
318/// IMPL NOTE: This structure is quite heavy, it may be necessary to box its inner state.
319#[derive(Debug)]
320pub struct ResReadDir {
321    /// Directory path that we are listing. It has no trailing separator!
322    dir_path: Arc<str>,
323    /// The native read dir result that maybe used for iteration before the package part.
324    native_read_dir: Option<ReadDir>,
325    /// The package read dir mode, yielded after the native read dir if present.
326    package_read_dir: Option<PackageReadDir>,
327}
328
329#[derive(Debug)]
330struct PackageReadDir {
331    /// Shared resource filesystem data.
332    shared: Arc<Shared>,
333    /// Directory index in the node cache.
334    dir_index: usize,
335    /// A vector containing all names to return on next iterations. Name is associated to
336    /// the node index in the cache, this
337    remaining_names: Vec<(Arc<str>, usize)>,
338    /// Total names count to return.
339    last_children_count: usize,
340    /// This keep the last (exclusive) node index used by children.
341    last_children_last_node_index: usize,
342}
343
344impl Iterator for ResReadDir {
345
346    type Item = io::Result<ResDirEntry>;
347
348    fn next(&mut self) -> Option<Self::Item> {
349
350        if let Some(native_read_dir) = &mut self.native_read_dir {
351            match native_read_dir.next() {
352                Some(Ok(entry)) => {
353                    // FIXME: Don't unwrap
354                    let file_name = entry.file_name();
355                    let file_type = entry.file_type().unwrap();
356                    let file_name = file_name.to_str().unwrap();
357                    return Some(Ok(ResDirEntry { 
358                        dir_path: Arc::clone(&self.dir_path), 
359                        name: Arc::from(file_name),
360                        is_dir: file_type.is_dir(),
361                    }))
362                },
363                Some(Err(e)) => return Some(Err(e)),
364                None => (),
365            }
366        }
367
368        if let Some(package_read_dir) = &mut self.package_read_dir {
369
370            // Then we search the directory iteratively, and loop over if a pending package
371            // has been opened.
372            let mut mutable = package_read_dir.shared.mutable.lock().unwrap();
373
374            loop {
375                    
376                let dir_info = mutable.node_cache.get_dir(package_read_dir.dir_index).unwrap();
377
378                // If the directory info has been updated since the last iteration, we need to 
379                // update remaining names. We need to do this kind of detection because we don't
380                // exclusively own the filesystem and other read/read_dir may have altered cache.
381                if dir_info.children.len() != package_read_dir.last_children_count {
382
383                    debug_assert!(dir_info.children.len() > package_read_dir.last_children_count);
384
385                    let mut max_child_index = 0;
386                    for (child_name, &child_index) in &dir_info.children {
387                        max_child_index = max_child_index.max(child_index);
388                        if child_index >= package_read_dir.last_children_last_node_index {
389                            package_read_dir.remaining_names.push((Arc::clone(child_name), child_index));
390                        }
391                    }
392
393                    package_read_dir.last_children_count = dir_info.children.len();
394                    package_read_dir.last_children_last_node_index = max_child_index + 1;
395
396                }
397
398                if let Some((node_name, node_index)) = package_read_dir.remaining_names.pop() {
399                    return Some(Ok(ResDirEntry {
400                        dir_path: Arc::clone(&self.dir_path),
401                        name: node_name,
402                        is_dir: mutable.node_cache.get_dir(node_index).is_some(),
403                    }));
404                }
405
406                // If there are no more file, we try opening more packages.
407                if !mutable.try_open_pending_package() {
408                    return None; // No more package to open, no more file to return.
409                }
410
411            }
412
413        }
414
415        None
416
417    }
418
419}
420
421/// Represent an file or directory entry returned by [`ResReadDir`].
422pub struct ResDirEntry {
423    dir_path: Arc<str>,
424    name: Arc<str>,
425    is_dir: bool,
426}
427
428impl ResDirEntry {
429
430    /// Return the entry name.
431    #[inline]
432    pub fn name(&self) -> &str {
433        &self.name
434    }
435
436    /// Reconstruct the full path of the entry for later [`ResFilesystem::read()`] call.
437    pub fn path(&self) -> String {
438        format!("{}/{}", self.dir_path, self.name)
439    }
440
441    /// Return true if this entry is a directory.
442    #[inline]
443    pub fn is_dir(&self) -> bool {
444        self.is_dir
445    }
446
447    /// Return true if this entry is a file.
448    #[inline]
449    pub fn is_file(&self) -> bool {
450        !self.is_dir
451    }
452
453}
454
455
456/// The node cache structure.
457#[derive(Debug)]
458struct NodeCache {
459    /// Inner file informations tree.
460    nodes: Vec<NodeInfo>,
461    /// Number of directories in all nodes.
462    dir_count: usize,
463    dir_children_max_count: usize,
464    node_name_max_len: usize,
465}
466
467/// Kind of cached node information, absent, file or directory node.
468#[derive(Debug)]
469enum NodeInfo {
470    // Information about a file.
471    File(FileInfo),
472    // Information about a directory.
473    Dir(DirInfo)
474}
475
476#[derive(Debug)]
477struct FileInfo {
478    // Index of the package that contains the file.
479    package_index: usize,
480    // Index of the file within the package.
481    file_index: usize,
482}
483
484#[derive(Debug, Default)]
485struct DirInfo {
486    /// Children of the directory. Key is a shared string because we clone it when 
487    /// iterating directory entries. If this is altered because of a package indexing,
488    /// the added children are guaranteed to have a node index that is greater than
489    /// any previous one.
490    children: BTreeMap<Arc<str>, usize>,
491}
492
493impl NodeCache {
494
495    /// Create a new default file cache.
496    fn new() -> Self {
497        Self {
498            nodes: vec![NodeInfo::Dir(DirInfo::default())],
499            dir_count: 0,
500            dir_children_max_count: 0,
501            node_name_max_len: 0,
502        }
503    }
504
505    /// Index a package in this node cache, note that the caller should avoid calling 
506    /// this twice for the same packages.
507    fn index_package(&mut self, package_index: usize, package_reader: &PackageReader<File>) {
508
509        let mut last_dir_index = 0;
510        let mut last_dir_path = ""; // This contains the end slash when relevant.
511
512        for (file_index, file_path) in package_reader.names().enumerate() {
513            
514            // Always split the file name from the rest of the directory path.
515            // NOTE: It is valid to split at 'index == file_path.len()', in this
516            // case the 'file_name' will be empty, but this should not happen!
517            // Also, 'dir_path' should not start with a sep.
518            let (mut dir_path, file_name) = match file_path.rfind('/') {
519                Some(last_sep_index) => file_path.split_at(last_sep_index + 1),
520                None => ("", file_path),
521            };
522
523            debug_assert!(!file_name.is_empty(), "package names should only contains files");
524
525            self.node_name_max_len = self.node_name_max_len.max(file_name.len());
526
527            // If the file don't start with the last dir path, then we can reset index
528            // to zero and try re-fetching all the path. If it starts with, then we just
529            // shorten the path.
530            let mut current_dir_index;
531            if dir_path.starts_with(last_dir_path) {
532                dir_path = &dir_path[last_dir_path.len()..];
533                current_dir_index = last_dir_index;
534            } else {
535                current_dir_index = 0;
536            }
537
538            // If dir path isn't empty, it must contain at least a slash at the end, and
539            // we discard it before splitting because we don't want to have a trailing
540            // empty 'dir_part'.
541            if !dir_path.is_empty() {
542                for dir_part in dir_path[..dir_path.len() - 1].split('/') {
543
544                    self.node_name_max_len = self.node_name_max_len.max(dir_part.len());
545
546                    // NOTE: Need to store the inner length here, we use it after to 
547                    // avoid borrowing issues.
548                    let inner_len = self.nodes.len();
549                    let dir = self.nodes[current_dir_index]
550                        .as_dir_mut()
551                        .expect("trying to make a directory where a file already exists");
552                    
553                    if let Some(&child_index) = dir.children.get(dir_part) {
554                        current_dir_index = child_index;
555                    } else {
556                        current_dir_index = inner_len;
557                        dir.children.insert(Arc::from(dir_part), inner_len);
558                        self.dir_children_max_count = self.dir_children_max_count.max(dir.children.len());
559                        self.nodes.push(NodeInfo::Dir(DirInfo::default()));
560                        self.dir_count += 1;
561                    }
562
563                }
564            }
565
566            if last_dir_index != current_dir_index {
567                last_dir_index = current_dir_index;
568                last_dir_path = dir_path;
569            }
570
571            // NOTE: Same as above!
572            let inner_len = self.nodes.len();
573            let dir = self.nodes[current_dir_index]
574                .as_dir_mut()
575                .expect("current directory should effectively be a directory");
576
577            let prev_child = dir.children.insert(Arc::from(file_name), inner_len);
578            self.dir_children_max_count = self.dir_children_max_count.max(dir.children.len());
579            debug_assert!(prev_child.is_none(), "overwriting a file");
580            self.nodes.push(NodeInfo::File(FileInfo {
581                package_index,
582                file_index,
583            }));
584
585        }
586
587    }
588
589    /// Find directory info in cache from the given file path, the directory path should
590    /// not contain trailing nor leading separator. The index of the node within internal
591    /// nodes array is also returned so that dir info can be retrieved faster a second
592    /// time, this index is guaranteed to be valid for the cache lifetime, cache is never
593    /// destroyed.
594    fn find_dir(&self, dir_path: &str) -> Option<(usize, &DirInfo)> {
595
596        let mut current_dir_index = 0;
597        if !dir_path.is_empty() {
598            for dir_part in dir_path.split('/') {
599                current_dir_index = *self.nodes[current_dir_index]
600                    .as_dir()?
601                    .children
602                    .get(dir_part)?;
603            }
604        }
605
606        self.nodes[current_dir_index]
607            .as_dir()
608            .map(|dir| (current_dir_index, dir))
609
610    }
611
612    /// Find file info in cache from the given file path. The index of the node is also
613    /// returned like for [`Self::find_dir`].
614    fn find_file(&self, file_path: &str) -> Option<(usize, &FileInfo)> {
615
616        // No exactly same as when indexing because here we don't care of last dir sep.
617        let (dir_path, file_name) = file_path.rsplit_once('/').unwrap_or(("", file_path));
618
619        let (_, dir) = self.find_dir(dir_path)?;
620        let file_index = *dir.children.get(file_name)?;
621        self.nodes[file_index]
622            .as_file()
623            .map(|file| (file_index, file))
624
625    }
626
627    /// Get a directory information from its node index (see [`Self::find_dir`]).
628    fn get_dir(&self, index: usize) -> Option<&DirInfo> {
629        self.nodes.get(index)?.as_dir()
630    }
631
632    // /// Get a file information from its node index (see [`Self::find_file`]).
633    // fn get_file(&self, index: usize) -> Option<&FileInfo> {
634    //     self.nodes.get(index)?.as_file()
635    // }
636
637}
638
639impl NodeInfo {
640
641    #[inline]
642    fn as_file(&self) -> Option<&FileInfo> {
643        match self {
644            NodeInfo::File(file) => Some(file),
645            NodeInfo::Dir(_) => None,
646        }
647    }
648
649    #[inline]
650    fn as_dir(&self) -> Option<&DirInfo> {
651        match self {
652            NodeInfo::File(_) => None,
653            NodeInfo::Dir(dir) => Some(dir),
654        }
655    }
656
657    // #[inline]
658    // fn as_file_mut(&mut self) -> Option<&mut FileInfo> {
659    //     match self {
660    //         NodeInfo::File(file) => Some(file),
661    //         NodeInfo::Dir(_) => None,
662    //     }
663    // }
664
665    #[inline]
666    fn as_dir_mut(&mut self) -> Option<&mut DirInfo> {
667        match self {
668            NodeInfo::File(_) => None,
669            NodeInfo::Dir(dir) => Some(dir),
670        }
671    }
672
673}