1use std::{
2 fmt,
3 io::{self, Read, Seek, SeekFrom},
4 sync::{Arc, Mutex, MutexGuard},
5 time::SystemTime,
6};
7
8use nwnrs_checksums::prelude::*;
9use nwnrs_compressedbuf::prelude::*;
10use nwnrs_exo::prelude::*;
11use nwnrs_resref::prelude::*;
12use tracing::instrument;
13
14pub const MEMORY_CACHE_THRESHOLD: usize = 1024 * 1024;
17
18pub trait ReadSeek: Read + Seek {}
20impl<T: Read + Seek> ReadSeek for T {}
21
22pub type SharedReadSeek = Arc<Mutex<Box<dyn ReadSeek + Send>>>;
24pub type ResIoSpawner =
26 Arc<dyn Fn() -> io::Result<Box<dyn ReadSeek + Send>> + Send + Sync + 'static>;
27
28#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CachePolicy {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
CachePolicy::Use => "Use",
CachePolicy::Bypass => "Bypass",
})
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for CachePolicy {
#[inline]
fn clone(&self) -> CachePolicy { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for CachePolicy { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for CachePolicy {
#[inline]
fn eq(&self, other: &CachePolicy) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CachePolicy {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::hash::Hash for CachePolicy {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
let __self_discr = ::core::intrinsics::discriminant_value(self);
::core::hash::Hash::hash(&__self_discr, state)
}
}Hash)]
29pub enum CachePolicy {
31 Use,
33 Bypass,
35}
36
37impl CachePolicy {
38 #[must_use]
40 pub const fn uses_cache(self) -> bool {
41 #[allow(non_exhaustive_omitted_patterns)] match self {
Self::Use => true,
_ => false,
}matches!(self, Self::Use)
42 }
43}
44
45#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ResManError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
ResManError::Io(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Io",
&__self_0),
ResManError::CompressedBuf(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"CompressedBuf", &__self_0),
ResManError::Message(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Message", &__self_0),
}
}
}Debug)]
46pub enum ResManError {
48 Io(io::Error),
50 CompressedBuf(CompressedBufError),
52 Message(String),
54}
55
56impl ResManError {
57 pub(crate) fn msg(message: impl Into<String>) -> Self {
58 Self::Message(message.into())
59 }
60}
61
62impl fmt::Display for ResManError {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Self::Io(error) => error.fmt(f),
66 Self::CompressedBuf(error) => error.fmt(f),
67 Self::Message(message) => f.write_str(message),
68 }
69 }
70}
71
72impl std::error::Error for ResManError {}
73
74impl From<io::Error> for ResManError {
75 fn from(value: io::Error) -> Self {
76 Self::Io(value)
77 }
78}
79
80impl From<CompressedBufError> for ResManError {
81 fn from(value: CompressedBufError) -> Self {
82 Self::CompressedBuf(value)
83 }
84}
85
86pub type ResManResult<T> = Result<T, ResManError>;
88
89#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ResOrigin {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "ResOrigin",
"container", &self.container, "label", &&self.label)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ResOrigin {
#[inline]
fn clone(&self) -> ResOrigin {
ResOrigin {
container: ::core::clone::Clone::clone(&self.container),
label: ::core::clone::Clone::clone(&self.label),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for ResOrigin {
#[inline]
fn eq(&self, other: &ResOrigin) -> bool {
self.container == other.container && self.label == other.label
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ResOrigin {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<String>;
}
}Eq)]
90pub struct ResOrigin {
95 container: String,
96 label: String,
97}
98
99impl ResOrigin {
100 pub fn new(container: impl Into<String>, label: impl Into<String>) -> Self {
102 Self {
103 container: container.into(),
104 label: label.into(),
105 }
106 }
107
108 #[must_use]
110 pub fn container(&self) -> &str {
111 &self.container
112 }
113
114 #[must_use]
116 pub fn label(&self) -> &str {
117 &self.label
118 }
119}
120
121impl fmt::Display for ResOrigin {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 if self.label.is_empty() {
124 f.write_str(&self.container)
125 } else {
126 f.write_fmt(format_args!("{0}({1})", self.container, self.label))write!(f, "{}({})", self.container, self.label)
127 }
128 }
129}
130
131pub(crate) enum ResBacking {
132 Shared(SharedReadSeek),
133 Spawned(ResIoSpawner),
134}
135
136pub(crate) struct ResMutableState {
137 pub cached: bool,
138 pub cache: Vec<u8>,
139 pub sha1: SecureHash,
140}
141
142pub(crate) struct ResInner {
143 pub mtime: SystemTime,
144 pub io_offset: u64,
145 pub io_size: i64,
146 pub resref: ResRef,
147 pub compression: ExoResFileCompressionType,
148 pub compressed_buf_algorithm: Option<Algorithm>,
149 pub uncompressed_size: usize,
150 pub origin: ResOrigin,
151 pub backing: ResBacking,
152 pub state: Mutex<ResMutableState>,
153}
154
155#[derive(#[automatically_derived]
impl ::core::clone::Clone for Res {
#[inline]
fn clone(&self) -> Res {
Res { inner: ::core::clone::Clone::clone(&self.inner) }
}
}Clone)]
156pub struct Res {
162 pub(crate) inner: Arc<ResInner>,
163}
164
165impl fmt::Debug for Res {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 f.debug_struct("Res")
168 .field("resref", &self.resref())
169 .field("origin", &self.origin())
170 .field("io_offset", &self.io_offset())
171 .field("io_size", &self.io_size())
172 .field("compression", &self.compression_algorithm())
173 .finish()
174 }
175}
176
177impl fmt::Display for Res {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 f.write_fmt(format_args!("{0}@{1}", self.resref(), self.origin()))write!(f, "{}@{}", self.resref(), self.origin())
180 }
181}
182
183impl Res {
184 #[allow(clippy::too_many_arguments)]
185 pub fn new_with_stream(
187 origin: ResOrigin,
188 resref: ResRef,
189 mtime: SystemTime,
190 io: SharedReadSeek,
191 io_size: i64,
192 io_offset: u64,
193 compression: ExoResFileCompressionType,
194 compressed_buf_algorithm: Option<Algorithm>,
195 uncompressed_size: usize,
196 sha1: SecureHash,
197 ) -> Self {
198 Self::new(
199 origin,
200 resref,
201 mtime,
202 ResBacking::Shared(io),
203 io_size,
204 io_offset,
205 compression,
206 compressed_buf_algorithm,
207 uncompressed_size,
208 sha1,
209 )
210 }
211
212 #[allow(clippy::too_many_arguments)]
213 pub fn new_with_spawner(
218 origin: ResOrigin,
219 resref: ResRef,
220 mtime: SystemTime,
221 io_spawner: ResIoSpawner,
222 io_size: i64,
223 io_offset: u64,
224 compression: ExoResFileCompressionType,
225 compressed_buf_algorithm: Option<Algorithm>,
226 uncompressed_size: usize,
227 sha1: SecureHash,
228 ) -> Self {
229 Self::new(
230 origin,
231 resref,
232 mtime,
233 ResBacking::Spawned(io_spawner),
234 io_size,
235 io_offset,
236 compression,
237 compressed_buf_algorithm,
238 uncompressed_size,
239 sha1,
240 )
241 }
242
243 #[allow(clippy::too_many_arguments)]
244 fn new(
245 origin: ResOrigin,
246 resref: ResRef,
247 mtime: SystemTime,
248 backing: ResBacking,
249 io_size: i64,
250 io_offset: u64,
251 compression: ExoResFileCompressionType,
252 compressed_buf_algorithm: Option<Algorithm>,
253 uncompressed_size: usize,
254 sha1: SecureHash,
255 ) -> Self {
256 let effective_uncompressed =
257 if compression == ExoResFileCompressionType::None && uncompressed_size == 0 {
258 usize::try_from(io_size.max(0)).unwrap_or(usize::MAX)
259 } else {
260 uncompressed_size
261 };
262
263 Self {
264 inner: Arc::new(ResInner {
265 mtime,
266 io_offset,
267 io_size,
268 resref,
269 compression,
270 compressed_buf_algorithm,
271 uncompressed_size: effective_uncompressed,
272 origin,
273 backing,
274 state: Mutex::new(ResMutableState {
275 cached: false,
276 cache: Vec::new(),
277 sha1,
278 }),
279 }),
280 }
281 }
282
283 #[must_use]
285 pub fn resref(&self) -> ResRef {
286 self.inner.resref.clone()
287 }
288
289 #[must_use]
291 pub fn mtime(&self) -> SystemTime {
292 self.inner.mtime
293 }
294
295 #[must_use]
297 pub fn io_offset(&self) -> u64 {
298 self.inner.io_offset
299 }
300
301 #[must_use]
306 pub fn io_size(&self) -> i64 {
307 self.inner.io_size
308 }
309
310 #[must_use]
312 pub fn cached(&self) -> bool {
313 self.lock_state().is_ok_and(|state| state.cached)
314 }
315
316 #[must_use]
318 pub fn uncompressed_size(&self) -> usize {
319 self.inner.uncompressed_size
320 }
321
322 #[must_use]
324 pub fn compression_algorithm(&self) -> ExoResFileCompressionType {
325 self.inner.compression
326 }
327
328 #[must_use]
331 pub fn compressed_buf_algorithm(&self) -> Option<Algorithm> {
332 self.inner.compressed_buf_algorithm
333 }
334
335 #[must_use]
337 pub fn origin(&self) -> ResOrigin {
338 self.inner.origin.clone()
339 }
340
341 #[must_use]
343 pub fn io_owned(&self) -> bool {
344 #[allow(non_exhaustive_omitted_patterns)] match self.inner.backing {
ResBacking::Shared(_) => true,
_ => false,
}matches!(self.inner.backing, ResBacking::Shared(_))
345 }
346
347 #[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: ResManResult<()> = loop {};
return __tracing_attr_fake_return;
}
{
self.with_stream(|stream|
{
stream.seek(SeekFrom::Start(self.inner.io_offset))?;
Ok(())
})
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/types.rs:356",
"nwnrs_resman::types", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/types.rs"),
::tracing_core::__macro_support::Option::Some(356u32),
::tracing_core::__macro_support::Option::Some("nwnrs_resman::types"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err, fields(resref = %self.inner.resref))]
357 pub fn seek(&self) -> ResManResult<()> {
358 self.with_stream(|stream| {
359 stream.seek(SeekFrom::Start(self.inner.io_offset))?;
360 Ok(())
361 })
362 }
363
364 #[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: ResManResult<Vec<u8>> =
loop {};
return __tracing_attr_fake_return;
}
{
if cache_policy.uses_cache() {
let state = self.lock_state()?;
if state.cached { return Ok(state.cache.clone()); }
}
let raw = self.read_raw()?;
let data =
match self.inner.compression {
ExoResFileCompressionType::None => raw,
ExoResFileCompressionType::CompressedBuf => {
decompress_bytes(&raw, EXO_RES_FILE_COMPRESSED_BUF_MAGIC)?
}
};
if cache_policy.uses_cache() &&
data.len() < MEMORY_CACHE_THRESHOLD {
let mut state = self.lock_state()?;
state.cached = true;
state.cache.clone_from(&data);
}
Ok(data)
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/types.rs:373",
"nwnrs_resman::types", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/types.rs"),
::tracing_core::__macro_support::Option::Some(373u32),
::tracing_core::__macro_support::Option::Some("nwnrs_resman::types"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err, fields(resref = %self.inner.resref, cache_policy = ?cache_policy))]
374 pub fn read_all(&self, cache_policy: CachePolicy) -> ResManResult<Vec<u8>> {
375 if cache_policy.uses_cache() {
376 let state = self.lock_state()?;
377 if state.cached {
378 return Ok(state.cache.clone());
379 }
380 }
381
382 let raw = self.read_raw()?;
383 let data = match self.inner.compression {
384 ExoResFileCompressionType::None => raw,
385 ExoResFileCompressionType::CompressedBuf => {
386 decompress_bytes(&raw, EXO_RES_FILE_COMPRESSED_BUF_MAGIC)?
387 }
388 };
389
390 if cache_policy.uses_cache() && data.len() < MEMORY_CACHE_THRESHOLD {
391 let mut state = self.lock_state()?;
392 state.cached = true;
393 state.cache.clone_from(&data);
394 }
395
396 Ok(data)
397 }
398
399 #[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: ResManResult<SecureHash> =
loop {};
return __tracing_attr_fake_return;
}
{
{
let state = self.lock_state()?;
if state.sha1 != EMPTY_SECURE_HASH {
return Ok(state.sha1);
}
}
let digest = secure_hash(self.read_all(CachePolicy::Use)?);
let mut state = self.lock_state()?;
state.sha1 = digest;
Ok(digest)
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/types.rs:408",
"nwnrs_resman::types", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/types.rs"),
::tracing_core::__macro_support::Option::Some(408u32),
::tracing_core::__macro_support::Option::Some("nwnrs_resman::types"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err, fields(resref = %self.inner.resref))]
409 pub fn sha1(&self) -> ResManResult<SecureHash> {
410 {
411 let state = self.lock_state()?;
412 if state.sha1 != EMPTY_SECURE_HASH {
413 return Ok(state.sha1);
414 }
415 }
416
417 let digest = secure_hash(self.read_all(CachePolicy::Use)?);
418 let mut state = self.lock_state()?;
419 state.sha1 = digest;
420 Ok(digest)
421 }
422
423 #[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: ResManResult<T> = loop {};
return __tracing_attr_fake_return;
}
{
match &self.inner.backing {
ResBacking::Shared(stream) => {
let mut stream =
stream.lock().map_err(|error|
{
ResManError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("shared res stream lock poisoned: {0}",
error))
}))
})?;
op(stream.as_mut())
}
ResBacking::Spawned(spawner) => {
let mut stream = spawner()?;
op(stream.as_mut())
}
}
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/types.rs:432",
"nwnrs_resman::types", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/types.rs"),
::tracing_core::__macro_support::Option::Some(432u32),
::tracing_core::__macro_support::Option::Some("nwnrs_resman::types"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err, fields(resref = %self.inner.resref))]
433 pub fn with_stream<T, F>(&self, op: F) -> ResManResult<T>
434 where
435 F: FnOnce(&mut dyn ReadSeek) -> ResManResult<T>,
436 {
437 match &self.inner.backing {
438 ResBacking::Shared(stream) => {
439 let mut stream = stream.lock().map_err(|error| {
440 ResManError::msg(format!("shared res stream lock poisoned: {error}"))
441 })?;
442 op(stream.as_mut())
443 }
444 ResBacking::Spawned(spawner) => {
445 let mut stream = spawner()?;
446 op(stream.as_mut())
447 }
448 }
449 }
450
451 fn read_raw(&self) -> ResManResult<Vec<u8>> {
452 self.with_stream(|stream| {
453 stream.seek(SeekFrom::Start(self.inner.io_offset))?;
454 if self.inner.io_size < 0 {
455 let mut data = Vec::new();
456 stream.read_to_end(&mut data)?;
457 Ok(data)
458 } else {
459 let data_len = usize::try_from(self.inner.io_size).map_err(|error| {
460 ResManError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("resource size out of range: {0}",
error))
})format!("resource size out of range: {error}"))
461 })?;
462 let mut data = ::alloc::vec::from_elem(0_u8, data_len)vec![0_u8; data_len];
463 stream.read_exact(&mut data)?;
464 Ok(data)
465 }
466 })
467 }
468
469 fn lock_state(&self) -> ResManResult<MutexGuard<'_, ResMutableState>> {
470 self.inner
471 .state
472 .lock()
473 .map_err(|error| ResManError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("res state lock poisoned: {0}",
error))
})format!("res state lock poisoned: {error}")))
474 }
475}
476
477pub trait ResContainer: fmt::Display + Send + Sync {
479 fn contains(&self, rr: &ResRef) -> bool;
481 fn demand(&self, rr: &ResRef) -> ResManResult<Res>;
488 fn count(&self) -> usize;
490 fn contents(&self) -> Vec<ResRef>;
492}
493
494pub fn new_res_origin(container: impl Into<String>, label: impl Into<String>) -> ResOrigin {
496 ResOrigin::new(container, label)
497}
498
499pub fn shared_stream<T>(stream: T) -> SharedReadSeek
501where
502 T: ReadSeek + Send + 'static,
503{
504 Arc::new(Mutex::new(Box::new(stream)))
505}