iOS: Avoid RefCell and static mut (#4255)

* iOS: Refactor queued_gpu_redraws out from AppStateImpl

To allow AppStateImpl to be Copy, and to move redraws into the window in
the future.

* iOS AppState: Avoid RefCell and static mut

Instead, prefer Cell and Copy types, as those will never have crashes
on re-entrancy / if forgetting to make a state transition.
This commit is contained in:
Mads Marquart
2025-06-07 23:16:41 +02:00
committed by GitHub
parent f1e0f6c646
commit e540062ac0
2 changed files with 115 additions and 184 deletions

View File

@@ -1,11 +1,11 @@
#![deny(unused_results)] #![deny(unused_results)]
use std::cell::{OnceCell, RefCell, RefMut}; use std::cell::{Cell, OnceCell};
use std::collections::HashSet; use std::collections::HashSet;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use std::{mem, ptr}; use std::{fmt, ptr};
use dispatch2::MainThreadBound; use dispatch2::MainThreadBound;
use dpi::PhysicalSize; use dpi::PhysicalSize;
@@ -32,12 +32,6 @@ macro_rules! bug {
}; };
} }
macro_rules! bug_assert {
($test:expr, $($msg:tt)*) => {
assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*))
};
}
/// Get the global event handler for the application. /// Get the global event handler for the application.
/// ///
/// This is stored separately from AppState, since AppState needs to be accessible while the handler /// This is stored separately from AppState, since AppState needs to be accessible while the handler
@@ -71,131 +65,90 @@ impl EventWrapper {
} }
// this is the state machine for the app lifecycle // this is the state machine for the app lifecycle
#[derive(Debug)] #[derive(Clone, Copy, Debug)]
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl { enum AppStateImpl {
Initial { Initial,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>, ProcessingEvents { active_control_flow: ControlFlow },
}, ProcessingRedraws { active_control_flow: ControlFlow },
ProcessingEvents { Waiting { start: Instant },
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
ProcessingRedraws {
active_control_flow: ControlFlow,
},
Waiting {
start: Instant,
},
PollFinished, PollFinished,
Terminated, Terminated,
} }
pub(crate) struct AppState { pub(crate) struct AppState {
// This should never be `None`, except for briefly during a state transition. state: Cell<AppStateImpl>,
app_state: Option<AppStateImpl>, control_flow: Cell<ControlFlow>,
control_flow: ControlFlow,
waker: EventLoopWaker, waker: EventLoopWaker,
event_loop_proxy: Arc<EventLoopProxy>, event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Vec<EventWrapper>, queued_events: Cell<Vec<EventWrapper>>,
queued_gpu_redraws: Cell<HashSet<Retained<WinitUIWindow>>>,
} }
impl fmt::Debug for AppState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AppState")
.field("control_flow", &self.control_flow)
.field("waker", &self.waker)
.field("event_loop_proxy", &self.event_loop_proxy)
.field("queued_events", &"Cell<...>")
.field("queued_gpu_redraws", &"Cell<...>")
.finish_non_exhaustive()
}
}
// SAFETY: Creating `MainThreadBound` in a `const` context,
// where there is no concept of the main thread.
static GLOBAL: MainThreadBound<OnceCell<AppState>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
impl AppState { impl AppState {
pub(crate) fn get_mut(mtm: MainThreadMarker) -> RefMut<'static, AppState> { pub(crate) fn setup_global(mtm: MainThreadMarker) -> bool {
// basically everything in UIKit requires the main thread, so it's pointless to use the let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
// std::sync APIs. get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
// must be mut because plain `static` requires `Sync` }));
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None); GLOBAL
.get(mtm)
#[allow(unknown_lints)] // New lint below .set(Self {
#[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead. state: Cell::new(AppStateImpl::Initial),
let mut guard = unsafe { APP_STATE.borrow_mut() }; control_flow: Cell::new(ControlFlow::default()),
if guard.is_none() { waker: EventLoopWaker::new(CFRunLoop::main().unwrap()),
#[inline(never)] event_loop_proxy,
#[cold] queued_events: Cell::new(Vec::new()),
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>, mtm: MainThreadMarker) { queued_gpu_redraws: Cell::new(HashSet::new()),
let waker = EventLoopWaker::new(CFRunLoop::main().unwrap()); })
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || { .is_ok()
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}));
**guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(),
waker,
event_loop_proxy,
queued_events: Vec::new(),
});
}
init_guard(&mut guard, mtm);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
} }
fn state(&self) -> &AppStateImpl { pub(crate) fn get(mtm: MainThreadMarker) -> &'static Self {
match &self.app_state { GLOBAL.get(mtm).get().expect("tried to get application state before it was registered")
Some(ref state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn state_mut(&mut self) -> &mut AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn take_state(&mut self) -> AppStateImpl {
match self.app_state.take() {
Some(state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn set_state(&mut self, new_state: AppStateImpl) {
bug_assert!(
self.app_state.is_none(),
"attempted to set an `AppState` without calling `take_state` first {:?}",
self.app_state
);
self.app_state = Some(new_state)
}
fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => mem::replace(state, new_state),
None => bug!("`AppState` previously failed a state transition"),
}
} }
fn has_launched(&self) -> bool { fn has_launched(&self) -> bool {
!matches!(self.state(), AppStateImpl::Initial { .. }) !matches!(self.state.get(), AppStateImpl::Initial)
} }
fn has_terminated(&self) -> bool { fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated) matches!(self.state.get(), AppStateImpl::Terminated)
} }
fn did_finish_launching_transition(&mut self) { fn did_finish_launching_transition(&self) {
let queued_gpu_redraws = match self.take_state() { match self.state.get() {
AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws, AppStateImpl::Initial => {},
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; }
self.set_state(AppStateImpl::ProcessingEvents { self.state
active_control_flow: self.control_flow, .set(AppStateImpl::ProcessingEvents { active_control_flow: self.control_flow.get() });
queued_gpu_redraws,
});
} }
fn wakeup_transition(&mut self) -> Option<StartCause> { fn wakeup_transition(&self) -> Option<StartCause> {
// before `AppState::did_finish_launching` is called, pretend there is no running // before `AppState::did_finish_launching` is called, pretend there is no running
// event loop. // event loop.
if !self.has_launched() || self.has_terminated() { if !self.has_launched() || self.has_terminated() {
return None; return None;
} }
let start_cause = match (self.control_flow, self.take_state()) { let start_cause = match (self.control_flow.get(), self.state.get()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll, (ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll,
(ControlFlow::Wait, AppStateImpl::Waiting { start }) => { (ControlFlow::Wait, AppStateImpl::Waiting { start }) => {
StartCause::WaitCancelled { start, requested_resume: None } StartCause::WaitCancelled { start, requested_resume: None }
@@ -210,66 +163,61 @@ impl AppState {
s => bug!("`EventHandler` unexpectedly woke up {:?}", s), s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.state
queued_gpu_redraws: Default::default(), .set(AppStateImpl::ProcessingEvents { active_control_flow: self.control_flow.get() });
active_control_flow: self.control_flow,
});
Some(start_cause) Some(start_cause)
} }
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> { fn main_events_cleared_transition(&self) {
let (queued_gpu_redraws, active_control_flow) = match self.take_state() { let active_control_flow = match self.state.get() {
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => { AppStateImpl::ProcessingEvents { active_control_flow } => active_control_flow,
(queued_gpu_redraws, active_control_flow)
},
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingRedraws { active_control_flow }); self.state.set(AppStateImpl::ProcessingRedraws { active_control_flow });
queued_gpu_redraws
} }
fn events_cleared_transition(&mut self) { fn events_cleared_transition(&self) {
if !self.has_launched() || self.has_terminated() { if !self.has_launched() || self.has_terminated() {
return; return;
} }
let old = match self.take_state() { let old = match self.state.get() {
AppStateImpl::ProcessingRedraws { active_control_flow } => active_control_flow, AppStateImpl::ProcessingRedraws { active_control_flow } => active_control_flow,
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
let new = self.control_flow; let new = self.control_flow.get();
match (old, new) { match (old, new) {
(ControlFlow::Wait, ControlFlow::Wait) => { (ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start }); self.state.set(AppStateImpl::Waiting { start });
self.waker.stop() self.waker.stop()
}, },
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant => if old_instant == new_instant =>
{ {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start }); self.state.set(AppStateImpl::Waiting { start });
}, },
(_, ControlFlow::Wait) => { (_, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start }); self.state.set(AppStateImpl::Waiting { start });
self.waker.stop() self.waker.stop()
}, },
(_, ControlFlow::WaitUntil(new_instant)) => { (_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start }); self.state.set(AppStateImpl::Waiting { start });
self.waker.start_at(new_instant) self.waker.start_at(new_instant)
}, },
// Unlike on macOS, handle Poll to Poll transition here to call the waker // Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => { (_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished); self.state.set(AppStateImpl::PollFinished);
self.waker.start() self.waker.start()
}, },
} }
} }
fn terminated_transition(&mut self) { fn terminated_transition(&self) {
match self.replace_state(AppStateImpl::Terminated) { match self.state.replace(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { .. } => {}, AppStateImpl::ProcessingEvents { .. } => {},
s => bug!("terminated while not processing events {:?}", s), s => bug!("terminated while not processing events {:?}", s),
} }
@@ -279,26 +227,27 @@ impl AppState {
&self.event_loop_proxy &self.event_loop_proxy
} }
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) { pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow = control_flow; self.control_flow.set(control_flow);
} }
pub(crate) fn control_flow(&self) -> ControlFlow { pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow self.control_flow.get()
} }
} }
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<WinitUIWindow>) { pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm); let this = AppState::get(mtm);
match this.state_mut() { match this.state.get() {
&mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. } AppStateImpl::Initial | AppStateImpl::ProcessingEvents { .. } => {
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => { let mut queued_gpu_redraws = this.queued_gpu_redraws.take();
let _ = queued_gpu_redraws.insert(window); let _ = queued_gpu_redraws.insert(window);
this.queued_gpu_redraws.set(queued_gpu_redraws);
}, },
s @ &mut AppStateImpl::ProcessingRedraws { .. } s @ AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. } | s @ AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished => bug!("unexpected state {:?}", s), | s @ AppStateImpl::PollFinished => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => { AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated") panic!("Attempt to create a `Window` after the app has terminated")
}, },
} }
@@ -313,14 +262,10 @@ pub(crate) fn launch<R>(
} }
pub fn did_finish_launching(mtm: MainThreadMarker) { pub fn did_finish_launching(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let this = AppState::get(mtm);
this.waker.start(); this.waker.start();
this.did_finish_launching_transition();
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
AppState::get_mut(mtm).did_finish_launching_transition();
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init)); get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm })); get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
@@ -329,12 +274,11 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
// AppState::did_finish_launching handles the special transition `Init` // AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) { pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let this = AppState::get(mtm);
let cause = match this.wakeup_transition() { let cause = match this.wakeup_transition() {
None => return, None => return,
Some(cause) => cause, Some(cause) => cause,
}; };
drop(this);
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause)); get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause));
handle_nonuser_events(mtm, []); handle_nonuser_events(mtm, []);
@@ -348,19 +292,20 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
mtm: MainThreadMarker, mtm: MainThreadMarker,
events: I, events: I,
) { ) {
let mut this = AppState::get_mut(mtm); let this = AppState::get(mtm);
if this.has_terminated() { if this.has_terminated() {
return; return;
} }
if !get_handler(mtm).ready() { if !get_handler(mtm).ready() {
// Prevent re-entrancy; queue the events up for once we're done handling the event instead. // Prevent re-entrancy; queue the events up for once we're done handling the event instead.
this.queued_events.extend(events); let mut queued_events = this.queued_events.take();
queued_events.extend(events);
this.queued_events.set(queued_events);
return; return;
} }
let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }); let processing_redraws = matches!(this.state.get(), AppStateImpl::ProcessingRedraws { .. });
drop(this);
for event in events { for event in events {
if !processing_redraws && event.is_redraw() { if !processing_redraws && event.is_redraw() {
@@ -375,12 +320,10 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
} }
loop { loop {
let mut this = AppState::get_mut(mtm); let queued_events = this.queued_events.take();
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() { if queued_events.is_empty() {
break; break;
} }
drop(this);
for event in queued_events { for event in queued_events {
if !processing_redraws && event.is_redraw() { if !processing_redraws && event.is_redraw() {
@@ -397,19 +340,16 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
} }
fn handle_user_events(mtm: MainThreadMarker) { fn handle_user_events(mtm: MainThreadMarker) {
let this = AppState::get_mut(mtm); let this = AppState::get(mtm);
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) { if matches!(this.state.get(), AppStateImpl::ProcessingRedraws { .. }) {
bug!("user events attempted to be sent out while `ProcessingRedraws`"); bug!("user events attempted to be sent out while `ProcessingRedraws`");
} }
drop(this);
loop { loop {
let mut this = AppState::get_mut(mtm); let queued_events = this.queued_events.take();
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() { if queued_events.is_empty() {
break; break;
} }
drop(this);
for event in queued_events { for event in queued_events {
handle_wrapped_event(mtm, event); handle_wrapped_event(mtm, event);
@@ -434,28 +374,23 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
} }
pub fn handle_main_events_cleared(mtm: MainThreadMarker) { pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let this = AppState::get(mtm);
if !this.has_launched() || this.has_terminated() { if !this.has_launched() || this.has_terminated() {
return; return;
} }
match this.state_mut() { match this.state.get() {
AppStateImpl::ProcessingEvents { .. } => {}, AppStateImpl::ProcessingEvents { .. } => {},
_ => bug!("`ProcessingRedraws` happened unexpectedly"), _ => bug!("`ProcessingRedraws` happened unexpectedly"),
}; };
drop(this);
handle_user_events(mtm); handle_user_events(mtm);
let mut this = AppState::get_mut(mtm); this.main_events_cleared_transition();
let redraw_events: Vec<EventWrapper> = this let queued_gpu_redraws = this.queued_gpu_redraws.take();
.main_events_cleared_transition() let redraw_events = queued_gpu_redraws.into_iter().map(|window| EventWrapper::Window {
.into_iter() window_id: window.id(),
.map(|window| EventWrapper::Window { event: WindowEvent::RedrawRequested,
window_id: window.id(), });
event: WindowEvent::RedrawRequested,
})
.collect();
drop(this);
handle_nonuser_events(mtm, redraw_events); handle_nonuser_events(mtm, redraw_events);
get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm })); get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm }));
@@ -463,7 +398,7 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
} }
pub fn handle_events_cleared(mtm: MainThreadMarker) { pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition(); AppState::get(mtm).events_cleared_transition();
} }
pub(crate) fn handle_resumed(mtm: MainThreadMarker) { pub(crate) fn handle_resumed(mtm: MainThreadMarker) {
@@ -496,11 +431,10 @@ pub(crate) fn terminated(application: &UIApplication) {
} }
handle_nonuser_events(mtm, events); handle_nonuser_events(mtm, events);
let mut this = AppState::get_mut(mtm); let this = AppState::get(mtm);
this.terminated_transition(); this.terminated_transition();
// Prevent EventLoopProxy from firing again. // Prevent EventLoopProxy from firing again.
this.event_loop_proxy.invalidate(); this.event_loop_proxy.invalidate();
drop(this);
get_handler(mtm).terminate(); get_handler(mtm).terminate();
} }
@@ -541,6 +475,7 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
(view, screen_frame) (view, screen_frame)
} }
#[derive(Debug)]
struct EventLoopWaker { struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>, timer: CFRetained<CFRunLoopTimer>,
} }
@@ -574,15 +509,15 @@ impl EventLoopWaker {
} }
} }
fn stop(&mut self) { fn stop(&self) {
self.timer.set_next_fire_date(f64::MAX); self.timer.set_next_fire_date(f64::MAX);
} }
fn start(&mut self) { fn start(&self) {
self.timer.set_next_fire_date(f64::MIN); self.timer.set_next_fire_date(f64::MIN);
} }
fn start_at(&mut self, instant: Instant) { fn start_at(&self, instant: Instant) {
let now = Instant::now(); let now = Instant::now();
if now >= instant { if now >= instant {
self.start(); self.start();

View File

@@ -39,7 +39,7 @@ pub struct ActiveEventLoop {
impl RootActiveEventLoop for ActiveEventLoop { impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy { fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(AppState::get_mut(self.mtm).event_loop_proxy().clone()) CoreEventLoopProxy::new(AppState::get(self.mtm).event_loop_proxy().clone())
} }
fn create_window( fn create_window(
@@ -73,7 +73,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn listen_device_events(&self, _allowed: DeviceEvents) {} fn listen_device_events(&self, _allowed: DeviceEvents) {}
fn set_control_flow(&self, control_flow: ControlFlow) { fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get_mut(self.mtm).set_control_flow(control_flow) AppState::get(self.mtm).set_control_flow(control_flow)
} }
fn system_theme(&self) -> Option<Theme> { fn system_theme(&self) -> Option<Theme> {
@@ -81,7 +81,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
} }
fn control_flow(&self) -> ControlFlow { fn control_flow(&self) -> ControlFlow {
AppState::get_mut(self.mtm).control_flow() AppState::get(self.mtm).control_flow()
} }
fn exit(&self) { fn exit(&self) {
@@ -146,13 +146,9 @@ impl EventLoop {
let mtm = MainThreadMarker::new() let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread"); .expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false; if !AppState::setup_global(mtm) {
unsafe { // Required, AppState is global state, and event loop can only be run once.
if SINGLETON_INIT { return Err(EventLoopError::RecreationAttempt);
// Required, AppState is global state, and event loop can only be run once.
return Err(EventLoopError::RecreationAttempt);
}
SINGLETON_INIT = true;
} }
// this line sets up the main run loop before `UIApplicationMain` // this line sets up the main run loop before `UIApplicationMain`