dear_imgui_rs/context/
suspended.rs1use std::panic::{self, AssertUnwindSafe};
2use std::ptr;
3
4use crate::clipboard::ClipboardContext;
5use crate::fonts::SharedFontAtlas;
6use crate::sys;
7
8use super::Context;
9use super::attachment::AttachmentRegistry;
10use super::binding::{
11 CTX_MUTEX, ContextBinding, ContextId, ContextState, ContextThreadLease,
12 bound_context_scope_active, clear_current_context, no_current_context, set_current_context,
13};
14use super::frame::FrameLifecycleState;
15use super::snapshot_hub::SnapshotHub;
16use super::texture_registry::ManagedTextureRegistry;
17use super::{
18 ContextActivationError, ContextActivationReason, ContextScopeError, ContextSuspensionError,
19 ContextSuspensionReason, ScopedActivationError,
20};
21
22impl Context {
23 pub fn suspend(self) -> Result<SuspendedContext, ContextSuspensionError> {
28 let _guard = CTX_MUTEX.lock();
29 if bound_context_scope_active() {
30 return Err(ContextSuspensionError::new(
31 self,
32 ContextSuspensionReason::BindingScopeActive,
33 ));
34 }
35 if !self.is_current_context() {
36 return Err(ContextSuspensionError::new(
37 self,
38 ContextSuspensionReason::NotCurrent,
39 ));
40 }
41 if self.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
42 return Err(ContextSuspensionError::new(
43 self,
44 ContextSuspensionReason::FrameOpen,
45 ));
46 }
47 clear_current_context();
48 Ok(SuspendedContext(self))
49 }
50
51 pub fn suspend_or_panic(self) -> SuspendedContext {
58 self.suspend()
59 .unwrap_or_else(|error| panic!("Context::suspend_or_panic(): {error}"))
60 }
61}
62
63#[derive(Debug)]
67pub struct SuspendedContext(pub(super) Context);
68
69impl SuspendedContext {
70 pub fn id(&self) -> ContextId {
72 self.0.id()
73 }
74
75 pub fn try_with_active<T, E>(
93 &mut self,
94 f: impl FnOnce(&mut Context) -> Result<T, E>,
95 ) -> Result<T, ScopedActivationError<E>> {
96 let _guard = CTX_MUTEX.lock();
97 if bound_context_scope_active() {
98 return Err(
99 ContextScopeError::Activation(ContextActivationReason::BindingScopeActive).into(),
100 );
101 }
102 if !no_current_context() {
103 return Err(ContextScopeError::Activation(
104 ContextActivationReason::ContextAlreadyActive,
105 )
106 .into());
107 }
108 let expected_id = self.0.id();
109 let expected_raw = self.0.raw;
110 let binding = self.0.binding();
111 binding
112 .try_with_bound_context_guarded(|bound| {
113 let result = panic::catch_unwind(AssertUnwindSafe(|| f(&mut self.0)));
114
115 debug_assert!(bound.previous_context().is_null());
116 if self.0.id() != expected_id || self.0.raw != expected_raw {
117 if let Err(payload) = result {
118 panic::resume_unwind(payload);
119 }
120 panic!(
121 "SuspendedContext::try_with_active(): closure moved or replaced the Context owner"
122 );
123 }
124
125 match result {
126 Ok(Ok(value)) => {
127 if self.0.end_frame_for_teardown_unlocked() {
128 return Err(ContextScopeError::FrameLeftOpen.into());
129 }
130 Ok(value)
131 }
132 Ok(Err(error)) => {
133 self.0.end_frame_for_teardown_unlocked();
134 Err(ScopedActivationError::Closure(error))
135 }
136 Err(payload) => {
137 let _ = panic::catch_unwind(AssertUnwindSafe(|| {
139 self.0.end_frame_for_teardown_unlocked();
140 }));
141 panic::resume_unwind(payload)
142 }
143 }
144 })
145 .map_err(|error| {
146 ScopedActivationError::Scope(ContextScopeError::ContextUnavailable(error))
147 })?
148 }
149
150 pub fn with_active_or_panic<T>(&mut self, f: impl FnOnce(&mut Context) -> T) -> T {
157 self.try_with_active(|context| Ok::<_, std::convert::Infallible>(f(context)))
158 .unwrap_or_else(|error| match error {
159 ScopedActivationError::Closure(never) => match never {},
160 ScopedActivationError::Scope(error) => {
161 panic!("SuspendedContext::with_active_or_panic(): {error}")
162 }
163 })
164 }
165
166 pub fn try_create() -> crate::error::ImGuiResult<Self> {
168 Self::try_create_internal(None)
169 }
170
171 pub fn try_create_with_shared_font_atlas(
179 shared_font_atlas: SharedFontAtlas,
180 ) -> crate::error::ImGuiResult<Self> {
181 Self::try_create_internal(Some(shared_font_atlas))
182 }
183
184 pub fn create() -> Self {
186 Self::try_create().expect("Failed to create Dear ImGui context")
187 }
188
189 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
195 Self::try_create_with_shared_font_atlas(shared_font_atlas)
196 .expect("Failed to create Dear ImGui context")
197 }
198
199 fn try_create_internal(
202 shared_font_atlas: Option<SharedFontAtlas>,
203 ) -> crate::error::ImGuiResult<Self> {
204 if bound_context_scope_active() {
205 return Err(crate::error::ImGuiError::ContextBindingScopeActive);
206 }
207 let thread_lease = ContextThreadLease::acquire()?;
208 let _guard = CTX_MUTEX.lock();
209 let previous_context = unsafe { sys::igGetCurrentContext() };
210
211 let shared_font_atlas_ptr = match &shared_font_atlas {
212 Some(atlas) => atlas.as_ptr(),
213 None => ptr::null_mut(),
214 };
215 crate::fonts::validate_font_atlas_context_registration(shared_font_atlas_ptr)?;
216
217 let id =
218 ContextId::allocate().ok_or_else(|| crate::error::ImGuiError::ContextCreation {
219 reason: "process Context identity space is exhausted".to_string(),
220 })?;
221
222 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
223 if raw.is_null() {
224 set_current_context(previous_context);
225 return Err(crate::error::ImGuiError::ContextCreation {
226 reason: "ImGui_CreateContext returned null".to_string(),
227 });
228 }
229
230 unsafe {
231 let io = sys::igGetIO_ContextPtr(raw);
232 assert!(
233 !io.is_null(),
234 "new ImGui context returned a null IO pointer"
235 );
236 crate::fonts::register_font_atlas_context((*io).Fonts, raw);
237 }
238
239 let state = ContextState::new(id, raw);
240 let texture_registry = ManagedTextureRegistry::new(id);
241 let ui = crate::ui::Ui::new(raw, ContextBinding::new(&state), texture_registry.clone());
242
243 let ctx = Context {
244 raw,
245 state,
246 _thread_lease: thread_lease,
247 attachments: AttachmentRegistry::default(),
248 snapshot_hub: SnapshotHub::new(id),
249 texture_registry,
250 shared_font_atlas,
251 ini_filename: None,
252 log_filename: None,
253 platform_name: None,
254 renderer_name: None,
255 clipboard_ctx: Box::new(ClipboardContext::dummy()),
256 ui,
257 };
258
259 if previous_context.is_null() {
260 clear_current_context();
261 } else {
262 set_current_context(previous_context);
263 }
264
265 Ok(SuspendedContext(ctx))
266 }
267
268 pub fn activate(self) -> Result<Context, ContextActivationError> {
273 let _guard = CTX_MUTEX.lock();
274 if bound_context_scope_active() {
275 return Err(ContextActivationError::new(
276 self,
277 ContextActivationReason::BindingScopeActive,
278 ));
279 }
280 if !no_current_context() {
281 return Err(ContextActivationError::new(
282 self,
283 ContextActivationReason::ContextAlreadyActive,
284 ));
285 }
286 set_current_context(self.0.raw);
287 Ok(self.0)
288 }
289
290 pub fn activate_or_panic(self) -> Context {
297 self.activate()
298 .unwrap_or_else(|error| panic!("SuspendedContext::activate_or_panic(): {error}"))
299 }
300}