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
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::{VecDeque};
use std::ops::Deref;
use std::rc::{Rc, Weak};

use piet::RenderContext;
use pax_properties_coproduct::{PropertiesCoproduct};
use pax_runtime_api::{Timeline};

use crate::{HandlerRegistry, RenderNodePtr, RenderNodePtrList, RenderTreeContext};


/// `Runtime` is a container for data and logic needed by the `Engine`,
/// explicitly aside from rendering.  For example, this is a home
/// for logic that manages scopes and stack frames.
pub struct Runtime<R: 'static + RenderContext> {
    stack: Vec<Rc<RefCell<StackFrame<R>>>>,

    /// Tracks the native ids (id_chain)s of clipping instances
    /// When a node is mounted, it may consult the clipping stack to see which clipping instances are relevant to it
    /// This list of `id_chain`s is passed along with `**Create`, in order to associate with the appropriate clipping elements on the native side
    clipping_stack: Vec<Vec<u64>>,
    native_message_queue: VecDeque<pax_message::NativeMessage>
}

impl<R: 'static + RenderContext> Runtime<R> {
    pub fn new() -> Self {
        Runtime {
            stack: vec![],
            clipping_stack: vec![],
            native_message_queue: VecDeque::new(),
        }
    }

    // NOTE: this value could be cached on stackframes, registered & cached during engine rendertree traversal (specifically: when stackframes are pushed)
    //       This would make id_chain resolution essentially free, O(1) instead of O(log(n))
    //       Profile first to understand the impact before optimizing
    pub fn get_list_of_repeat_indicies_from_stack(&self) -> Vec<u64> {
        let mut indices: Vec<u64> = vec![];

        self.stack.iter().for_each(|frame_wrapped|{
            if let PropertiesCoproduct::RepeatItem(datum, i) = &*(*(*(*frame_wrapped).borrow_mut()).borrow().properties).borrow() {
                indices.push(*i as u64)
            }
        });
        indices
    }

    //return current state of native message queue, passing in a freshly initialized queue for next frame
    pub fn take_native_message_queue(&mut self) -> VecDeque<pax_message::NativeMessage> {
        std::mem::take(&mut self.native_message_queue)
    }

    pub fn enqueue_native_message(&mut self, msg: pax_message::NativeMessage) {
        self.native_message_queue.push_back(msg);
    }

    /// Return a pointer to the top StackFrame on the stack,
    /// without mutating the stack or consuming the value
    pub fn peek_stack_frame(&mut self) -> Option<Rc<RefCell<StackFrame<R>>>> {
        if self.stack.len() > 0 {
            Some(Rc::clone(&self.stack[&self.stack.len() - 1]))
        }else{
            None
        }
    }

    /// Remove the top element from the stack.  Currently does
    /// nothing with the value of the popped StackFrame.
    pub fn pop_stack_frame(&mut self){
        self.stack.pop(); //NOTE: handle value here if needed
    }

    /// Add a new frame to the stack, passing a list of adoptees
    /// that may be handled by `Slot` and a scope that includes the PropertiesCoproduct of the associated Component
    pub fn push_stack_frame(&mut self, flattened_adoptees: RenderNodePtrList<R>, properties: Rc<RefCell<PropertiesCoproduct>>, timeline: Option<Rc<RefCell<Timeline>>>) {
        let parent = self.peek_stack_frame().as_ref().map(Rc::downgrade);

        self.stack.push(
            Rc::new(RefCell::new(
                StackFrame::new(flattened_adoptees, properties, parent, timeline)
            ))
        );
    }

    pub fn push_clipping_stack_id(&mut self, id_chain: Vec<u64>) {
        self.clipping_stack.push(id_chain);
    }

    pub fn pop_clipping_stack_id(&mut self) {
        self.clipping_stack.pop();
    }

    pub fn get_current_clipping_ids(&self) -> Vec<Vec<u64>> {
        self.clipping_stack.clone()
    }

    /// Handles special-cases like `for`/`Repeat`, where properties for the
    /// control flow primitive need to be computed out-of-lifecycle, and where nested child elements
    /// need to be treated as top-level elements.
    /// For example, for `<Stacker><Ellipse />for i in (0..3){ <Rectangle /> }</Stacker>`,
    /// without this special handling `Stacker` will receive only two adoptees: the `Ellipse` and the `Repeat` node
    /// created by `for`.  In other words `for`s children need to be treated as `<Stacker>`s children,
    /// and this processing allows that to happpen.
    /// Note that this must be recursive to handle nested cases of flattening, for example nested `for` loops
    pub fn process__should_flatten__adoptees_recursive(adoptee: &RenderNodePtr<R>, rtc: &mut RenderTreeContext<R>) -> Vec<RenderNodePtr<R>> {
        let mut adoptee_borrowed = (**adoptee).borrow_mut();
        if adoptee_borrowed.should_flatten() {
            //1. this is an `if` or `for` (etc.) — it needs its properties computed
            //   in order for its children to be correct
            adoptee_borrowed.compute_properties(rtc);
            //2. recurse into top-level should_flatten() nodes
            (*adoptee_borrowed.get_rendering_children()).borrow().iter().map(|top_level_child_node|{
                Runtime::process__should_flatten__adoptees_recursive(top_level_child_node, rtc)
            }).flatten().collect()
            //NOTE: probably worth optimizing (pending profiling.)  Lots of allocation happening here -- flattening and collecting `Vec`s is probably not
            //the most efficient possible approach, and this is fairly hot-running code.
        } else {
            vec![Rc::clone(adoptee)]
        }
    }
}


/// Data structure for a single frame of our runtime stack, including
/// a reference to its parent frame, a list of `adoptees` for
/// prospective [`Slot`] consumption, and `properties` for
/// runtime evaluation, e.g. of Expressions.  StackFrames also track
/// timeline playhead position.
///
/// `Component`s push StackFrames when mounting and pop them when unmounting, thus providing a
/// hierarchical store of node-relevant data that can be bound to symbols, e.g. in expressions.
/// Note that `RepeatItem`s also push `StackFrame`s, because `RepeatItem` uses a `Component` internally.
pub struct StackFrame<R: 'static + RenderContext>
{
    adoptees: RenderNodePtrList<R>,
    properties: Rc<RefCell<PropertiesCoproduct>>,
    parent: Option<Weak<RefCell<StackFrame<R>>>>,
    timeline: Option<Rc<RefCell<Timeline>>>,
}

impl<R: 'static + RenderContext> StackFrame<R> {
    pub fn new(adoptees: RenderNodePtrList<R>, properties: Rc<RefCell<PropertiesCoproduct>>, parent: Option<Weak<RefCell<StackFrame<R>>>>, timeline: Option<Rc<RefCell<Timeline>>>) -> Self {
        StackFrame {
            adoptees,
            properties,
            parent,
            timeline,
        }
    }

    pub fn get_timeline_playhead_position(&self) -> usize {
        match &self.timeline {
            None => {
                //if this stackframe doesn't carry a timeline, then refer
                //to the parent stackframe's timeline (and recurse)
                match &self.parent {
                    Some(parent_frame) => {
                        (*parent_frame.upgrade().unwrap()).borrow().get_timeline_playhead_position()
                    },
                    None => 0
                }
            },
            Some(timeline) => {
                (**timeline).borrow().playhead_position
            }
        }
    }

    // Traverses stack recursively `n` times to retrieve ancestor;
    // useful for runtime lookups for identifiers
    pub fn peek_nth(&self, n: isize) -> Option<Rc<RefCell<StackFrame<R>>>> {
        if n == 0 {
            //0th ancestor is self; handle by caller since caller owns the Rc containing `self`
            None
        } else {
            self.recurse_peek_nth(n, 0)
        }
    }

    fn recurse_peek_nth(&self, n: isize, depth: isize) -> Option<Rc<RefCell<StackFrame<R>>>> {
        let new_depth = depth + 1;
        let parent = self.parent.as_ref().unwrap();
        if new_depth == n {
            return Some(parent.upgrade().unwrap());
        }
        (*parent.deref().upgrade().unwrap()).borrow().recurse_peek_nth(n, new_depth)
    }

    pub fn get_properties(&self) -> Rc<RefCell<PropertiesCoproduct>> {
        Rc::clone(&self.properties)
    }

    pub fn get_unflattened_adoptees(&self) -> RenderNodePtrList<R> {
        Rc::clone(&self.adoptees)
    }

    pub fn nth_adoptee(&self, n: usize) -> Option<RenderNodePtr<R>> {
        match (*self.adoptees).borrow().get(n) {
            Some(i) => {Some(Rc::clone(i))}
            None => {None}
        }
    }

    pub fn has_adoptees(&self) -> bool {
        (*self.adoptees).borrow().len() > 0
    }

}