logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! # 🧰 Resource API
//!
//! This API is useful for embedding and accessing static resources in a module.
//!
//! ## Example usage
//!
//! In `Cargo.toml` for the module, define which static file resource the module wants to be able to access.
//!
//! ```toml
//! [package.metadata.ark]
//! include-files = [
//!     "assets/coin.wav",
//! ]
//! ```
//!
//! This will associate the file "assets/coin.wav" as a dependent static resource of the Ark module and
//! Ark will load it and make it available for the module before it is loaded.
//!
//! Inside the module one can then use `ark::resource::get("assets/coin.wav")` to get a slice to the
//! contents of the resource. This data is always loaded in memory and as such always available for the module.
//!
//! ## File roots
//!
//! If one has a shared workspace with multiple modules and want to share resources between them, or have a more
//! complex path for them, one can set one or multiple file roots for the included files in `Cargo.toml`.
//!
//! In the below example we have an `assets` folder that is located in a shared workspace directory relative to the
//! `Cargo.toml` file and where we want to include files from. Note that in this example the resource would be referenced
//! in the module with the `ark::resource::get("assets/coin.wav")`
//!
//! ```toml
//! [package.metadata.ark]
//! include-file-roots = [
//!     "../../assets",
//! include-files = [
//!     "coin.wav",
//! ]
//! ```
//!
//! It is also possible to have multiple roots specified. The file will then be loaded from the first root it is found in
//!
//! ## Alternatives
//!
//! This mechanism is meant for resources that are not trivially small (say large than 1 KB) and enables Ark to compress, de-duplicate and
//! cache the resources efficiently for distribution, storage, and loading.
//!
//! But if one has very small file resources, where compression and caching is not as beneficial, then it can be more optimal and easier
//! to embed the file directly into the module binary instead with the Rust standard [`include_bytes!`] macro

use crate::ffi::ErrorCode;

#[doc(hidden)]
pub use crate::ffi::resource_v1::API as FFI_API;

pub use crate::ffi::resource_v1::ResourceHandleRepr;

/// Retrieves named static resource
pub fn get(name: &'static str) -> Option<&'static [u8]> {
    get_dynamic(name)
}

/// Retrieves named static resource in a unsafe manner
///
/// Takes a non-'static str as parameter.
/// [`get`] should be preferred over this.
pub fn get_dynamic(name: &str) -> Option<&'static [u8]> {
    match crate::ffi::resource_v0::get_static(name) {
        Ok(r) => Some(unsafe { std::slice::from_raw_parts(r.ptr as *const u8, r.size as usize) }),
        Err(ErrorCode::NotFound) => None,
        Err(error_code) => panic!(
            "Resource get '{}' failed with code {}",
            name, error_code as u32
        ),
    }
}

/// Represents a handle to unique resource
///
/// In-process serialization is supported through `speedy`. However, this
/// handle should not be persisted across module sessions.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
pub struct ResourceHandle {
    inner: ResourceHandleRepr,
}

impl ResourceHandle {
    /// Request the latest version of a resource given the provided name
    ///
    /// This will start downloading the resource if it isn't present in Ark
    #[inline]
    pub fn request(name: &str) -> Self {
        let inner = crate::ffi::resource_v1::request_resource_by_name(name);
        Self { inner }
    }

    /// Retrieve the resource's data by copying it into Wasm memory
    ///
    /// Returns [`None`] if the resource hasn't finished downloading
    ///
    /// If you only want to check if a resource is ready, use [`Self::is_ready`] instead
    #[inline]
    pub fn try_retrieve(&self) -> Option<Vec<u8>> {
        match crate::ffi::resource_v1::try_retrieve(self.inner) {
            Ok(bytes) => Some(bytes),
            Err(ErrorCode::Unavailable) => None,
            Err(error_code) => panic!(
                "Failed retrieving resource bytes with code: {}",
                error_code as u32
            ), // TODO: display name?
        }
    }

    /// Check if the resource is available for use
    #[inline]
    pub fn is_ready(&self) -> bool {
        match crate::ffi::resource_v1::is_ready(self.inner) {
            Ok(()) => true,
            Err(ErrorCode::Unavailable) => false,
            Err(error_code) => panic!("Failed `ResourceHandle::is_ready`: {}", error_code as u32),
        }
    }

    /// Retrieve the raw inner [`ResourceHandleRepr`]
    ///
    /// Useful for passing to other Ark APIs that can operate on resources
    ///
    /// TODO: return a concrete type that can be shared between Ark APIs
    #[inline]
    pub fn raw(&self) -> ResourceHandleRepr {
        self.inner
    }
}