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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//! Frames ensure Julia's garbage collector is properly managed.
//!
//! Julia data is freed by the GC when it's not in use. You will need to use frames to do things
//! like calling Julia functions and creating new values, this ensures the values created with a
//! specific frame are protected from garbage collection until that frame goes out of scope.
//!
//! Four different kinds of frames exist; [`StaticFrame`], [`DynamicFrame`], [`NullFrame`], and
//! [`AsyncFrame`]. The first two of them can be nested and freely mixed. The main difference
//! between those two is that a [`StaticFrame`] is created with a definite capacity, while a
//! [`DynamicFrame`] will dynamically grow its capacity whenever a value is created or a function
//!  is called. A [`StaticFrame`] is more efficient, a [`DynamicFrame`] is easier to use. Creating
//! a nested frame takes no space in the current frame.
//!
//! The third type, [`NullFrame`] can only be used if you call Rust from Julia. They don't
//! allocate at all and can only be used to borrow array data.
//!
//! The final type, [`AsyncFrame`] is only available when you use the async runtime. Structs that
//! implement [`JuliaTask`] can use this kind of frame in the `run`-method. It's essentially a
//! [`DynamicFrame`] with the additional feature that it can be used to call
//! [`Value::call_async`].
//!
//! Frames have a lifetime, `'frame`. This lifetime ensures that a [`Value`] can only be used as
//! long as the frame that protects it has not been dropped.
//!
//! Most functionality that frames implement is defined by the [`Frame`] trait.
//!
//! [`StaticFrame`]: struct.StaticFrame.html
//! [`DynamicFrame`]: struct.DynamicFrame.html
//! [`NullFrame`]: struct.NullFrame.html
//! [`AsyncFrame`]: struct.AsyncFrame.html
//! [`Value`]: ../value/struct.Value.html
//! [`Value::call_async`]: ../value/struct.Value.html#method.call_async
//! [`Frame`]: ../traits/trait.Frame.html
//! [`JuliaTask`]: ../traits/multitask/trait.JuliaTask.html

use crate::error::JlrsResult;
#[cfg(all(feature = "async", target_os = "linux"))]
use crate::mode::Async;
use crate::mode::Mode;
use crate::stack::{Dynamic, StackView, Static};
use crate::CCall;
use std::marker::PhantomData;

#[derive(Copy, Clone, Default)]
pub struct FrameIdx(pub(crate) usize);

/// A `StaticFrame` is a frame that has a definite number of slots on the GC stack. With some
/// exceptions, creating new `Value`s and calling them require one slot each. Rather than using
/// new slots on the GC stack when a slot is needed, a `StaticFrame` uses the slots it acquired on
/// creation. See the documentation in the [`value`] module for more information about the costs.
/// You get access to a `StaticFrame` by calling [`Julia::frame`] or [`Frame::frame`], most of
/// their functionality is defined in the [`Frame`] trait.
///
/// [`value`]: ../value/index.html
/// [`Julia::frame`]: ../struct.Julia.html#method.frame
/// [`Frame::frame`]: ../traits/trait.Frame.html#method.frame
/// [`Frame`]: ../traits/trait.Frame.html
pub struct StaticFrame<'frame, U>
where
    U: Mode,
{
    pub(crate) idx: FrameIdx,
    pub(crate) memory: StackView<'frame, U, Static>,
    pub(crate) capacity: usize,
    pub(crate) len: usize,
}

impl<'frame, M: Mode> StaticFrame<'frame, M> {
    pub(crate) unsafe fn with_capacity(
        idx: FrameIdx,
        capacity: usize,
        memory: StackView<'frame, M, Static>,
    ) -> StaticFrame<'frame, M> {
        StaticFrame {
            idx,
            memory,
            capacity,
            len: 0,
        }
    }

    pub(crate) unsafe fn nested_frame<'nested>(
        &'nested mut self,
        capacity: usize,
    ) -> JlrsResult<StaticFrame<'nested, M>> {
        let idx = self.memory.new_frame(capacity)?;
        Ok(StaticFrame {
            idx,
            memory: self.memory.nest_static(),
            capacity,
            len: 0,
        })
    }

    /// Returns the total number of slots.
    pub fn capacity(&self) -> usize {
        self.capacity
    }
}

impl<'frame, U> Drop for StaticFrame<'frame, U>
where
    U: Mode,
{
    fn drop(&mut self) {
        unsafe {
            self.memory.pop_frame(self.idx);
        }
    }
}

/// A `DynamicFrame` is a frame that has a dynamic number of slots on the GC stack. With some
/// exceptions, creating new `Value`s and calling them require one slot each. A `DynamicFrame`
/// acquires a new slot every time one is needed. See the documentation in the [`value`] module
/// for more information about the costs. You get access to a `DynamicFrame` by calling
/// [`Julia::dynamic_frame`] or [`Frame::dynamic_frame`], most of
/// their functionality is defined in the [`Frame`] trait.
///
/// [`value`]: ../value/index.html
/// [`Julia::dynamic_frame`]: ../struct.Julia.html#method.dynamic_frame
/// [`Frame::dynamic_frame`]: ../traits/trait.Frame.html#method.dynamic_frame
/// [`Frame`]: ../traits/trait.Frame.html
pub struct DynamicFrame<'frame, U>
where
    U: Mode,
{
    pub(crate) idx: FrameIdx,
    pub(crate) memory: StackView<'frame, U, Dynamic>,
    pub(crate) len: usize,
}

impl<'frame, M: Mode> DynamicFrame<'frame, M> {
    pub(crate) unsafe fn new(idx: FrameIdx, memory: StackView<'frame, M, Dynamic>) -> Self {
        DynamicFrame {
            idx,
            memory,
            len: 0,
        }
    }

    pub(crate) unsafe fn nested_frame<'nested>(
        &'nested mut self,
    ) -> JlrsResult<DynamicFrame<'nested, M>> {
        let idx = self.memory.new_frame()?;
        Ok(DynamicFrame {
            idx,
            memory: self.memory.nest_dynamic(),
            len: 0,
        })
    }
}

impl<'frame, U> Drop for DynamicFrame<'frame, U>
where
    U: Mode,
{
    fn drop(&mut self) {
        unsafe {
            self.memory.pop_frame(self.idx);
        }
    }
}

/// An `Output` is a slot of a frame that has been reserved for later use. It can be used to
/// extend the lifetime of the result of a function call to the `Output`'s lifetime. You can
/// create an output by calling [`Frame::output`].
///
/// [`Frame::output`]: ../traits/trait.Frame.html#method.output
pub struct Output<'frame> {
    pub(crate) offset: usize,
    _marker: PhantomData<&'frame ()>,
}

impl<'frame> Output<'frame> {
    pub(crate) unsafe fn new(offset: usize) -> Self {
        Output {
            offset,
            _marker: PhantomData,
        }
    }
}

/// A `NullFrame` can be used if you call Rust from Julia through `ccall` and want to borrow array
/// data but not perform any allocations. It can't be nested or be used for functions that
/// allocate (like creating new values or calling functions). Functions that depend on allocation
/// will return `JlrsError::NullFrame` if you call them with a `NullFrame`.
pub struct NullFrame<'frame>(PhantomData<&'frame ()>);

impl<'frame> NullFrame<'frame> {
    pub(crate) unsafe fn new(_: &'frame mut CCall) -> Self {
        NullFrame(PhantomData)
    }
}

/// An `AsyncFrame` is a special kind of `DynamicFrame` that's available when you implement
/// [`JuliaTask`]. In addition to the capabilities of a `DynamicFrame` it can be used to call
/// [`Value::call_async`] which lets you call a function in a new thread in Julia. This feature is
/// only available by using [`AsyncJulia`].
///
/// [`Value::call_async`]: ../value/struct.Value.html#method.call_async
/// [`JuliaTask`]: ../traits/multitask/trait.JuliaTask.html
/// [`AsyncJulia`]: ../multitask/struct.AsyncJulia.html
#[cfg(all(feature = "async", target_os = "linux"))]
pub struct AsyncFrame<'frame> {
    pub(crate) idx: FrameIdx,
    pub(crate) memory: StackView<'frame, Async, Dynamic>,
    pub(crate) len: usize,
}

#[cfg(all(feature = "async", target_os = "linux"))]
impl<'frame> AsyncFrame<'frame> {
    pub(crate) unsafe fn nested_frame<'nested>(
        &'nested mut self,
    ) -> JlrsResult<DynamicFrame<'nested, Async>> {
        let idx = self.memory.new_frame()?;
        Ok(DynamicFrame {
            idx,
            memory: self.memory.nest_dynamic(),
            len: 0,
        })
    }
}

#[cfg(all(feature = "async", target_os = "linux"))]
impl<'frame> Drop for AsyncFrame<'frame> {
    fn drop(&mut self) {
        unsafe {
            self.memory.pop_frame(self.idx);
        }
    }
}