@@ -3,9 +3,19 @@ use crate::io::{
33 Reader , ReaderNotSeekableError , SeekableReader , Writer ,
44} ;
55use async_fs:: { read_dir, File } ;
6+ #[ cfg( not( target_os = "windows" ) ) ]
7+ use async_io:: Timer ;
8+ #[ cfg( not( target_os = "windows" ) ) ]
9+ use async_lock:: { Semaphore , SemaphoreGuard } ;
610use futures_lite:: StreamExt ;
711
812use alloc:: { borrow:: ToOwned , boxed:: Box } ;
13+ #[ cfg( target_os = "windows" ) ]
14+ use core:: marker:: PhantomData ;
15+ #[ cfg( not( target_os = "windows" ) ) ]
16+ use core:: time:: Duration ;
17+ #[ cfg( not( target_os = "windows" ) ) ]
18+ use futures_util:: { future, pin_mut} ;
919use std:: path:: Path ;
1020
1121use super :: { FileAssetReader , FileAssetWriter } ;
@@ -16,28 +26,97 @@ impl Reader for File {
1626 }
1727}
1828
29+ // Set to OS default limit / 2
30+ // macos & ios: 256
31+ // linux & android: 1024
32+ #[ cfg( any( target_os = "macos" , target_os = "ios" ) ) ]
33+ static OPEN_FILE_LIMITER : Semaphore = Semaphore :: new ( 128 ) ;
34+ #[ cfg( not( any( target_os = "macos" , target_os = "ios" , target_os = "windows" ) ) ) ]
35+ static OPEN_FILE_LIMITER : Semaphore = Semaphore :: new ( 512 ) ;
36+
37+ #[ cfg( not( target_os = "windows" ) ) ]
38+ async fn maybe_get_semaphore < ' a > ( ) -> Option < SemaphoreGuard < ' a > > {
39+ let guard_future = OPEN_FILE_LIMITER . acquire ( ) ;
40+ let timeout_future = Timer :: after ( Duration :: from_millis ( 500 ) ) ;
41+ pin_mut ! ( guard_future) ;
42+ pin_mut ! ( timeout_future) ;
43+
44+ match future:: select ( guard_future, timeout_future) . await {
45+ future:: Either :: Left ( ( guard, _) ) => Some ( guard) ,
46+ future:: Either :: Right ( ( _, _) ) => None ,
47+ }
48+ }
49+
50+ struct GuardedFile < ' a > {
51+ file : File ,
52+ #[ cfg( not( target_os = "windows" ) ) ]
53+ _guard : Option < SemaphoreGuard < ' a > > ,
54+ #[ cfg( target_os = "windows" ) ]
55+ _lifetime : PhantomData < & ' a ( ) > ,
56+ }
57+
58+ impl < ' a > futures_io:: AsyncRead for GuardedFile < ' a > {
59+ fn poll_read (
60+ mut self : core:: pin:: Pin < & mut Self > ,
61+ cx : & mut core:: task:: Context < ' _ > ,
62+ buf : & mut [ u8 ] ,
63+ ) -> core:: task:: Poll < std:: io:: Result < usize > > {
64+ core:: pin:: Pin :: new ( & mut self . file ) . poll_read ( cx, buf)
65+ }
66+ }
67+
68+ impl < ' a > Reader for GuardedFile < ' a > {
69+ fn seekable ( & mut self ) -> Result < & mut dyn SeekableReader , ReaderNotSeekableError > {
70+ self . file . seekable ( )
71+ }
72+ }
73+
1974impl AssetReader for FileAssetReader {
2075 async fn read < ' a > ( & ' a self , path : & ' a Path ) -> Result < impl Reader + ' a , AssetReaderError > {
76+ #[ cfg( not( target_os = "windows" ) ) ]
77+ let _guard = maybe_get_semaphore ( ) . await ;
78+
2179 let full_path = self . root_path . join ( path) ;
22- File :: open ( & full_path) . await . map_err ( |e| {
23- if e. kind ( ) == std:: io:: ErrorKind :: NotFound {
24- AssetReaderError :: NotFound ( full_path)
25- } else {
26- e. into ( )
27- }
28- } )
80+ File :: open ( & full_path)
81+ . await
82+ . map_err ( |e| {
83+ if e. kind ( ) == std:: io:: ErrorKind :: NotFound {
84+ AssetReaderError :: NotFound ( full_path)
85+ } else {
86+ e. into ( )
87+ }
88+ } )
89+ . map ( |file| GuardedFile {
90+ file,
91+ #[ cfg( not( target_os = "windows" ) ) ]
92+ _guard,
93+ #[ cfg( target_os = "windows" ) ]
94+ _lifetime : PhantomData :: default ( ) ,
95+ } )
2996 }
3097
3198 async fn read_meta < ' a > ( & ' a self , path : & ' a Path ) -> Result < impl Reader + ' a , AssetReaderError > {
99+ #[ cfg( not( target_os = "windows" ) ) ]
100+ let _guard = maybe_get_semaphore ( ) . await ;
101+
32102 let meta_path = get_meta_path ( path) ;
33103 let full_path = self . root_path . join ( meta_path) ;
34- File :: open ( & full_path) . await . map_err ( |e| {
35- if e. kind ( ) == std:: io:: ErrorKind :: NotFound {
36- AssetReaderError :: NotFound ( full_path)
37- } else {
38- e. into ( )
39- }
40- } )
104+ File :: open ( & full_path)
105+ . await
106+ . map_err ( |e| {
107+ if e. kind ( ) == std:: io:: ErrorKind :: NotFound {
108+ AssetReaderError :: NotFound ( full_path)
109+ } else {
110+ e. into ( )
111+ }
112+ } )
113+ . map ( |file| GuardedFile {
114+ file,
115+ #[ cfg( not( target_os = "windows" ) ) ]
116+ _guard,
117+ #[ cfg( target_os = "windows" ) ]
118+ _lifetime : PhantomData :: default ( ) ,
119+ } )
41120 }
42121
43122 async fn read_directory < ' a > (
0 commit comments