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
use std::{
any::{Any, TypeId},
cell::{Ref, RefCell},
collections::HashMap,
rc::Rc,
};
use crate::{
message_channel::nativeshell_init_message_channel_context, util::black_box, GetMessageChannel,
};
use super::RunLoop;
pub struct Context {
internal: Rc<ContextInternal>,
outermost: bool,
}
pub struct ContextInternal {
run_loop: RunLoop,
attachments: RefCell<HashMap<TypeId, (Box<dyn Any>, usize /* insertion order */)>>,
}
impl Context {
/// Creates a new context. The will be be associated with current thread and can
/// be retrieved at any point while the instance is in scope by [`Context::get()`].
///
/// Any NativeShell application must have exactly one context active.
pub fn new() -> Self {
let internal = Rc::new(ContextInternal {
run_loop: RunLoop::new(),
attachments: RefCell::new(HashMap::new()),
});
let res = Self {
internal: internal.clone(),
outermost: true,
};
let res_fallback = Self {
internal,
outermost: true,
};
ffi_methods();
let result = res.clone();
let prev_context = CURRENT_CONTEXT.with(|c| c.replace(Some(res)));
if prev_context.is_some() {
panic!("another context is already associated with current thread.");
}
CURRENT_CONTEXT_FALLBACK.with(|c| c.replace(Some(res_fallback)));
result.message_channel();
result
}
pub fn run_loop(&self) -> &RunLoop {
&self.internal.run_loop
}
pub fn get_attachment<T: Any, F: FnOnce() -> T>(&self, on_init: F) -> Ref<T> {
let id = TypeId::of::<T>();
// Do a separate check here, make sure attachments is not borrowed while
// creating the attachment
if !self.internal.attachments.borrow().contains_key(&id) {
let attachment = Box::new(on_init());
// store len to preserve insertion order; This will be used when dropping
let len = self.internal.attachments.borrow().len();
self.internal
.attachments
.borrow_mut()
.insert(id, (attachment, len));
}
let map = self.internal.attachments.borrow();
Ref::map(map, |r| {
let any = r.get(&id).unwrap();
any.0.downcast_ref::<T>().unwrap()
})
}
/// Returns context associated with current thread. Can only be called
/// on main thread and only while the original (outer-most) context is
/// still in scope. Otherwise the function will panic.
pub fn get() -> Self {
Self::current().expect("no context is associated with current thread.")
}
/// Returns context associated with current thread.
pub fn current() -> Option<Self> {
// It is necessary to be able to access Context::current() while thread locals
// are being destructed in case attachment destructor wants to access context.
// Which ever thread local is removed first will clean the attachment.
let res = CURRENT_CONTEXT.try_with(|c| c.borrow().as_ref().map(|c| c.clone()));
match res {
Ok(res) => res,
// (Can happen if attachment accesses context during destruction.)
// CURRENT_CONTEXT is being destroyed, use the fallback; Reverse situation
// can not happen because attachments will be removed by CURRENT_CONTEXT_FALLBACK
// destructor.
Err(_) => CURRENT_CONTEXT_FALLBACK.with(|c| c.borrow().as_ref().map(|c| c.clone())),
}
}
#[cfg(feature = "mock")]
/// Helper function for unit tests. Will create temporary context,
/// run the future in run loop and wait until completed.
pub fn run_test(future: impl futures::Future + 'static) {
let context = Context::new();
Context::get().run_loop().spawn(async {
future.await;
Context::get().run_loop().stop();
});
context.run_loop().run();
}
}
thread_local! {
static CURRENT_CONTEXT: RefCell<Option<Context>> = RefCell::new(None);
static CURRENT_CONTEXT_FALLBACK: RefCell<Option<Context>> = RefCell::new(None);
}
impl Drop for Context {
fn drop(&mut self) {
if self.outermost {
// Remove attachment in reverse order in which they were inserted
while self.internal.attachments.borrow().len() > 0 {
let to_remove_index = self.internal.attachments.borrow().len() - 1;
let to_remove = self
.internal
.attachments
.borrow()
.iter()
.find(|e| e.1 .1 == to_remove_index)
.map(|a| *a.0)
.expect("Attachment to remove not found");
// Hold removed item until RefMut gets dropped.
let _removed = { self.internal.attachments.borrow_mut().remove(&to_remove) };
if to_remove_index == 0 {
break;
}
}
CURRENT_CONTEXT.try_with(|c| c.take()).ok();
CURRENT_CONTEXT_FALLBACK.try_with(|c| c.take()).ok();
}
}
}
impl Context {
// Intentionally private
fn clone(&self) -> Context {
Context {
internal: self.internal.clone(),
outermost: false,
}
}
}
fn ffi_methods() {
// this ensures that all FFI methods are referenced and not removed by linker
if black_box(false) {
nativeshell_init_message_channel_context(std::ptr::null_mut());
}
}