libktx_rs/
stream.rs

1// Copyright (C) 2021 Paolo Jovon <paolo.jovon@gmail.com>
2// SPDX-License-Identifier: Apache-2.0
3
4//! A Rust-based KTX-Software I/O stream.
5
6use crate::sys::*;
7use log;
8use std::{
9    ffi::c_void,
10    fmt::Debug,
11    io::{Read, Seek, SeekFrom, Write},
12    marker::PhantomData,
13};
14
15/// Represents a Rust byte stream, i.e. something [`Read`], [`Write`] and [`Seek`].
16pub trait RWSeekable: Read + Write + Seek {
17    /// Upcasts self to a `RWSeekable` reference.
18    ///
19    /// This is required for getting a fat pointer to `self` to be stored in the
20    /// C-managed [`ktxStream`].
21    fn as_mut_dyn(&mut self) -> &mut dyn RWSeekable;
22}
23
24impl<T: Read + Write + Seek> RWSeekable for T {
25    fn as_mut_dyn(&mut self) -> &mut dyn RWSeekable {
26        self
27    }
28}
29
30impl<'a> Debug for dyn RWSeekable + 'a {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        write!(f, "RWSeekable({:p})", self)
33    }
34}
35
36/// A Rust-based `ktxStream`, for reading from / writing to [`RWSeekable`]s.
37#[allow(unused)]
38pub struct RustKtxStream<'a, T: RWSeekable + ?Sized + 'a> {
39    inner_ptr: Option<*mut T>,
40    ktx_stream: Option<Box<ktxStream>>,
41    ktx_phantom: PhantomData<&'a ktxStream>,
42}
43
44impl<'a, T: RWSeekable + ?Sized + 'a> RustKtxStream<'a, T> {
45    /// Attempts to create a new Rust-based `ktxStream`, wrapping the given `inner` [`RWSeekable`].
46    pub fn new(inner: Box<T>) -> Result<Self, ktx_error_code_e> {
47        let inner_ptr = Box::into_raw(inner);
48        // SAFETY: Safe, we just destructed a Box
49        let inner_rwseekable_ptr = unsafe { (*inner_ptr).as_mut_dyn() } as *mut dyn RWSeekable;
50        // SAFETY: Here be (rustc-version-dependent) dragons
51        let (t_addr, vtable_addr): (*mut c_void, *mut c_void) =
52            unsafe { std::mem::transmute(inner_rwseekable_ptr) };
53
54        let mut ktx_stream = Box::new(ktxStream {
55            read: Some(ktxRustStream_read),
56            skip: Some(ktxRustStream_skip),
57            write: Some(ktxRustStream_write),
58            getpos: Some(ktxRustStream_getpos),
59            setpos: Some(ktxRustStream_setpos),
60            getsize: Some(ktxRustStream_getsize),
61            destruct: Some(ktxRustStream_destruct),
62            // Prevent the C API from messing with Rust structs
63            closeOnDestruct: false,
64            // SAFETY: This should be safe. The C API only sees an opaque handle at the end of the day.
65            type_: streamType_eStreamTypeCustom,
66            data: unsafe { std::mem::zeroed() },
67            readpos: 0,
68        });
69        let custom_ptr = unsafe { ktx_stream.data.custom_ptr.as_mut() };
70        custom_ptr.address = t_addr;
71        custom_ptr.allocatorAddress = vtable_addr;
72        custom_ptr.size = 0;
73
74        Ok(Self {
75            inner_ptr: Some(inner_ptr),
76            ktx_stream: Some(ktx_stream),
77            ktx_phantom: PhantomData,
78        })
79    }
80
81    /// Returns a handle to the underlying [`ktxStream`].
82    ///
83    /// ## Safety
84    /// The returned handle is only for interaction with the C API.
85    /// Do not modify this in any way if not absolutely necessary!
86    pub fn ktx_stream(&self) -> *mut ktxStream {
87        match &self.ktx_stream {
88            // SAFETY - Safe. Even if C wants a mutable pointer.
89            // This acts like a RefCell, where the normal interior mutability rules do not apply.
90            Some(boxed) => unsafe { std::mem::transmute(boxed.as_ref()) },
91            None => std::ptr::null_mut(),
92        }
93    }
94
95    /// Returns a reference to the inner [`RWSeekable`].
96    pub fn inner(&self) -> &T {
97        // SAFETY: Safe if self has not been dropped
98        unsafe { &*self.inner_ptr.expect("Self was destroyed") as &T }
99    }
100
101    /// Returns a mutable reference to the inner [`RWSeekable`].
102    pub fn inner_mut(&mut self) -> &mut T {
103        // SAFETY: Safe if self has not been dropped
104        unsafe { &mut *self.inner_ptr.expect("Self was destroyed") as &mut T }
105    }
106
107    /// Zero out [`self.inner_ptr`], and re-box it to where it was before `new()`.
108    fn rebox_inner_ptr(&mut self) -> Box<T> {
109        // SAFETY: Safe-ish - a zeroed-out pointer is a null pointer in all supported platforms
110        let moved_t = std::mem::replace(&mut self.inner_ptr, unsafe { std::mem::zeroed() });
111        unsafe {
112            // SAFETY: Safe - we're just reconstructing the box that was destructed in Self::new()
113            Box::from_raw(moved_t.expect("Self was already destroyed"))
114        }
115    }
116
117    /// Destroys self, giving back the boxed [`RWSeekable`] that was passed to [`Self::new`].
118    pub fn into_inner(mut self) -> Box<T> {
119        self.rebox_inner_ptr()
120    }
121}
122
123impl<'a, T: RWSeekable + ?Sized + 'a> Drop for RustKtxStream<'a, T> {
124    fn drop(&mut self) {
125        // Firstly, this swaps self with a dummy
126        let mut moved_self = std::mem::replace(
127            self,
128            RustKtxStream {
129                inner_ptr: None,
130                ktx_stream: None,
131                ktx_phantom: PhantomData,
132            },
133        );
134
135        // This is to mark the C-land `ktxStream` as invalid, and then to deallocate it
136        if let Some(mut ktx_stream) = std::mem::replace(&mut moved_self.ktx_stream, None) {
137            let mut custom_ptr = unsafe { ktx_stream.data.custom_ptr.as_mut() };
138            custom_ptr.address = std::ptr::null_mut();
139            custom_ptr.allocatorAddress = std::ptr::null_mut();
140            custom_ptr.size = 0xBADDA7A;
141            std::mem::drop(ktx_stream);
142        }
143        // The drop() of `ktx_stream` will do the rest
144
145        // This is to destroy inner if `into_inner()` hasn't been called yet
146        if let Some(_) = moved_self.inner_ptr {
147            std::mem::drop(moved_self.rebox_inner_ptr())
148        }
149
150        // Finally, this prevents a drop cycle - IMPORTANT!
151        // Note that we manually destroyed all fields above
152        std::mem::forget(moved_self);
153    }
154}
155
156fn format_option_ptr<T>(f: &mut std::fmt::Formatter<'_>, option: &Option<T>) -> std::fmt::Result {
157    match option {
158        Some(t) => write!(f, "{:p}", t),
159        None => write!(f, "<none>"),
160    }
161}
162
163impl<'a, T: RWSeekable + ?Sized + 'a> Debug for RustKtxStream<'a, T> {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        write!(f, "RustKtxStream(inner=")?;
166        format_option_ptr(f, &self.inner_ptr)?;
167        write!(f, ", ktxStream=")?;
168        format_option_ptr(f, &self.ktx_stream)?;
169        write!(f, ")")
170    }
171}
172
173/// Get back a reference to the [`RWSeekable`] we put in `ktxStream.data.custom_ptr`. on RustKtxStream construction.
174/// SAFETY: UB if `str` is not actually a pointer to a [`RustKtxStream`].
175unsafe fn inner_rwseekable<'a>(str: *mut ktxStream) -> &'a mut dyn RWSeekable {
176    let fat_t_ptr = {
177        let custom_ptr = (*str).data.custom_ptr.as_ref();
178        (custom_ptr.address, custom_ptr.allocatorAddress)
179    };
180    let inner_ref: *mut dyn RWSeekable = std::mem::transmute(fat_t_ptr);
181    &mut *inner_ref
182}
183
184// Since `#[feature(seek_stream_len)]` is unstable...
185fn stream_len(seek: &mut dyn RWSeekable) -> std::io::Result<u64> {
186    let old_pos = seek.stream_position()?;
187    let size = seek.seek(SeekFrom::End(0))?;
188    seek.seek(SeekFrom::Start(old_pos))?;
189    Ok(size)
190}
191
192#[no_mangle]
193unsafe extern "C" fn ktxRustStream_read(
194    str: *mut ktxStream,
195    dst: *mut c_void,
196    count: ktx_size_t,
197) -> ktx_error_code_e {
198    let inner = inner_rwseekable(str);
199    let buf = std::slice::from_raw_parts_mut(dst as *mut u8, count as usize);
200    match inner.read_exact(buf) {
201        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
202        Err(err) => {
203            log::error!("ktxRustStream_read: {}", err);
204            ktx_error_code_e_KTX_FILE_READ_ERROR
205        }
206    }
207}
208
209#[no_mangle]
210unsafe extern "C" fn ktxRustStream_skip(
211    str: *mut ktxStream,
212    count: ktx_size_t,
213) -> ktx_error_code_e {
214    let inner = inner_rwseekable(str);
215    match inner.seek(SeekFrom::Current(count as i64)) {
216        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
217        Err(err) => {
218            log::error!("ktxRustStream_skip: {}", err);
219            ktx_error_code_e_KTX_FILE_SEEK_ERROR
220        }
221    }
222}
223
224#[no_mangle]
225unsafe extern "C" fn ktxRustStream_write(
226    str: *mut ktxStream,
227    src: *const c_void,
228    size: ktx_size_t,
229    count: ktx_size_t,
230) -> ktx_error_code_e {
231    let inner = inner_rwseekable(str);
232    let len = (size * count) as usize;
233    let buf = std::slice::from_raw_parts(src as *const u8, len);
234    match inner.write_all(buf) {
235        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
236        Err(err) => {
237            log::error!("ktxRustStream_write: {}", err);
238            ktx_error_code_e_KTX_FILE_WRITE_ERROR
239        }
240    }
241}
242
243#[no_mangle]
244unsafe extern "C" fn ktxRustStream_getpos(
245    str: *mut ktxStream,
246    pos: *mut ktx_off_t,
247) -> ktx_error_code_e {
248    let inner = inner_rwseekable(str);
249    match inner.stream_position() {
250        Ok(cur) => {
251            *pos = cur as ktx_off_t;
252            ktx_error_code_e_KTX_SUCCESS
253        }
254        Err(err) => {
255            log::error!("ktxRustStream_getpos: {}", err);
256            ktx_error_code_e_KTX_FILE_SEEK_ERROR
257        }
258    }
259}
260
261#[no_mangle]
262unsafe extern "C" fn ktxRustStream_setpos(str: *mut ktxStream, off: ktx_off_t) -> ktx_error_code_e {
263    let inner = inner_rwseekable(str);
264    match inner.seek(SeekFrom::Start(off as u64)) {
265        Ok(_) => ktx_error_code_e_KTX_SUCCESS,
266        Err(err) => {
267            log::error!("ktxRustStream_setpos: {}", err);
268            ktx_error_code_e_KTX_FILE_SEEK_ERROR
269        }
270    }
271}
272
273#[no_mangle]
274unsafe extern "C" fn ktxRustStream_getsize(
275    str: *mut ktxStream,
276    size: *mut ktx_size_t,
277) -> ktx_error_code_e {
278    let inner = inner_rwseekable(str);
279    match stream_len(inner) {
280        Ok(len) => {
281            *size = len as ktx_size_t;
282            ktx_error_code_e_KTX_SUCCESS
283        }
284        Err(err) => {
285            log::error!("ktxRustStream_getsize: {}", err);
286            ktx_error_code_e_KTX_FILE_SEEK_ERROR
287        }
288    }
289}
290
291#[no_mangle]
292unsafe extern "C" fn ktxRustStream_destruct(_str: *mut ktxStream) {
293    // No-op; `RustKtxStream::drop()` will do all the work.
294}