1use std::{env, mem, time::Duration};
23#[cfg(not(target_arch = "wasm32"))]
4use std::time::Instant;
56#[cfg(target_arch = "wasm32")]
7use web_time::Instant;
89use parking_lot::Mutex;
10use zng_txt::Txt;
1112use crate::{VIEW_MODE, VIEW_SERVER, VIEW_VERSION};
1314/// Configuration for starting a view-process.
15#[derive(Clone, Debug)]
16pub struct ViewConfig {
17/// The [`VERSION`] of the API crate in the app-process.
18 ///
19 /// [`VERSION`]: crate::VERSION
20pub version: Txt,
2122/// Name of the initial channel used in [`connect_view_process`] to setup the connections to the
23 /// client app-process.
24 ///
25 /// [`connect_view_process`]: crate::ipc::connect_view_process
26pub server_name: Txt,
2728/// If the server should consider all window requests, headless window requests.
29pub headless: bool,
30}
31impl ViewConfig {
32/// Reads config from environment variables set by the [`Controller`] in a view-process instance.
33 ///
34 /// View API implementers should call this to get the config when it suspects that is running as a view-process.
35 /// Returns `Some(_)` if the process was initialized as a view-process.
36 ///
37 /// [`Controller`]: crate::Controller
38pub fn from_env() -> Option<Self> {
39if let (Ok(version), Ok(server_name)) = (env::var(VIEW_VERSION), env::var(VIEW_SERVER)) {
40let headless = env::var(VIEW_MODE).map(|m| m == "headless").unwrap_or(false);
41Some(ViewConfig {
42 version: Txt::from_str(&version),
43 server_name: Txt::from_str(&server_name),
44 headless,
45 })
46 } else {
47None
48}
49 }
5051/// Returns `true` if the current process is awaiting for the config to start the
52 /// view process in the same process.
53pub(crate) fn is_awaiting_same_process() -> bool {
54matches!(*same_process().lock(), SameProcess::Awaiting)
55 }
5657/// Sets and unblocks the same-process config if there is a request.
58 ///
59 /// # Panics
60 ///
61 /// If there is no pending `wait_same_process`.
62pub(crate) fn set_same_process(cfg: ViewConfig) {
63if Self::is_awaiting_same_process() {
64*same_process().lock() = SameProcess::Ready(cfg);
65 } else {
66unreachable!("use `waiting_same_process` to check, then call `set_same_process` only once")
67 }
68 }
6970/// Wait for config from same-process.
71 ///
72 /// View API implementers should call this to sign that view-process config should be send to the same process
73 /// and then start the "app-process" code path in a different thread. This function returns when the app code path sends
74 /// the "view-process" configuration.
75pub fn wait_same_process() -> Self {
76let _s = tracing::trace_span!("ViewConfig::wait_same_process").entered();
7778if !matches!(*same_process().lock(), SameProcess::Not) {
79panic!("`wait_same_process` can only be called once");
80 }
8182*same_process().lock() = SameProcess::Awaiting;
8384let time = Instant::now();
85let timeout = Duration::from_secs(5);
86let sleep = Duration::from_millis(10);
8788while Self::is_awaiting_same_process() {
89 std::thread::sleep(sleep);
90if time.elapsed() >= timeout {
91panic!("timeout, `wait_same_process` waited for `{timeout:?}`");
92 }
93 }
9495match mem::replace(&mut *same_process().lock(), SameProcess::Done) {
96 SameProcess::Ready(cfg) => cfg,
97_ => unreachable!(),
98 }
99 }
100101/// Assert that the [`VERSION`] is the same in the app-process and view-process.
102 ///
103 /// This method must be called in the view-process implementation, it fails if the versions don't match, panics if
104 /// `is_same_process` or writes to *stderr* and exits with code .
105 ///
106 /// [`VERSION`]: crate::VERSION
107pub fn assert_version(&self, is_same_process: bool) {
108if self.version != crate::VERSION {
109let msg = format!(
110"view API version is not equal, app-process: {}, view-process: {}",
111self.version,
112crate::VERSION
113 );
114if is_same_process {
115panic!("{}", msg)
116 } else {
117eprintln!("{msg}");
118 zng_env::exit(i32::from_le_bytes(*b"vapi"));
119 }
120 }
121 }
122123/// Returns `true` if a view-process exited because of [`assert_version`].
124 ///
125 /// [`assert_version`]: Self::assert_version
126pub fn is_version_err(exit_code: Option<i32>, stderr: Option<&str>) -> bool {
127 exit_code.map(|e| e == i32::from_le_bytes(*b"vapi")).unwrap_or(false)
128 || stderr.map(|s| s.contains("view API version is not equal")).unwrap_or(false)
129 }
130}
131132enum SameProcess {
133 Not,
134 Awaiting,
135 Ready(ViewConfig),
136 Done,
137}
138139// because some view libs are dynamically loaded this variable needs to be patchable.
140//
141// This follows the same idea as the "hot-reload" patches, just manually implemented.
142static mut SAME_PROCESS: &Mutex<SameProcess> = &SAME_PROCESS_COLD;
143static SAME_PROCESS_COLD: Mutex<SameProcess> = Mutex::new(SameProcess::Not);
144145fn same_process() -> &'static Mutex<SameProcess> {
146// SAFETY: this is safe because SAME_PROCESS is only mutated on dynamic lib init, before any other code.
147unsafe { *std::ptr::addr_of!(SAME_PROCESS) }
148}
149150/// Dynamic view-process "same process" implementations must patch the static variables used by
151/// the view-api. This patch also propagates the tracing and log contexts.
152pub struct StaticPatch {
153 same_process: *const Mutex<SameProcess>,
154 tracing: tracing_shared::SharedLogger,
155}
156impl StaticPatch {
157/// Called in the main executable.
158pub fn capture() -> Self {
159Self {
160 same_process: same_process(),
161 tracing: tracing_shared::SharedLogger::new(),
162 }
163 }
164165/// Called in the dynamic library.
166 ///
167 /// # Safety
168 ///
169 /// Only safe if it is the first view-process code to run in the dynamic library.
170pub unsafe fn install(&self) {
171// SAFETY: safety handled by the caller
172unsafe {
173*std::ptr::addr_of_mut!(SAME_PROCESS) = &*self.same_process;
174 }
175self.tracing.install();
176 }
177}