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
use std::{
env, thread,
time::{Duration, Instant},
};
use zng_txt::Txt;
use crate::{VIEW_MODE, VIEW_SERVER, VIEW_VERSION};
/// Configuration for starting a view-process.
#[derive(Clone, Debug)]
pub struct ViewConfig {
/// The [`VERSION`] of the API crate in the app-process.
///
/// [`VERSION`]: crate::VERSION
pub version: Txt,
/// Name of the initial channel used in [`connect_view_process`] to setup the connections to the
/// client app-process.
///
/// [`connect_view_process`]: crate::ipc::connect_view_process
pub server_name: Txt,
/// If the server should consider all window requests, headless window requests.
pub headless: bool,
}
impl ViewConfig {
/// Reads config from environment variables set by the [`Controller`] in a view-process instance.
///
/// View API implementers should call this to get the config when it suspects that is running as a view-process.
/// Returns `Some(_)` if the process was initialized as a view-process.
///
/// [`Controller`]: crate::Controller
pub fn from_env() -> Option<Self> {
if let (Ok(version), Ok(server_name)) = (env::var(VIEW_VERSION), env::var(VIEW_SERVER)) {
let headless = env::var(VIEW_MODE).map(|m| m == "headless").unwrap_or(false);
Some(ViewConfig {
version: Txt::from_str(&version),
server_name: Txt::from_str(&server_name),
headless,
})
} else {
None
}
}
/// Returns `true` if the current process is awaiting for the config to start the
/// view process in the same process.
pub(crate) fn is_awaiting_same_process() -> bool {
env::var_os(Self::SAME_PROCESS_VAR).unwrap_or_default() == Self::SG_WAITING
}
/// Sets and unblocks the same-process config if there is a request.
///
/// # Panics
///
/// If there is no pending `wait_same_process`.
pub(crate) fn set_same_process(cfg: ViewConfig) {
if Self::is_awaiting_same_process() {
let cfg = format!("{}\n{}\n{}", cfg.version, cfg.server_name, cfg.headless);
env::set_var(Self::SAME_PROCESS_VAR, cfg);
} else {
unreachable!("use `waiting_same_process` to check, then call `set_same_process` only once")
}
}
/// Wait for config from same-process.
///
/// View API implementers should call this to sign that view-process config should be send to the same process
/// and then start the "app-process" code path in a different thread. This function returns when the app code path sends
/// the "view-process" configuration.
pub fn wait_same_process() -> Self {
let _s = tracing::trace_span!("ViewConfig::wait_same_process").entered();
if env::var_os(Self::SAME_PROCESS_VAR).is_some() {
panic!("`wait_same_process` can only be called once");
}
env::set_var(Self::SAME_PROCESS_VAR, Self::SG_WAITING);
let time = Instant::now();
let timeout = Duration::from_secs(5);
while Self::is_awaiting_same_process() {
thread::yield_now();
if time.elapsed() >= timeout {
panic!("timeout, `wait_same_process` waited for `{timeout:?}`");
}
}
let config = env::var(Self::SAME_PROCESS_VAR).unwrap();
env::set_var(Self::SAME_PROCESS_VAR, Self::SG_DONE);
let config: Vec<_> = config.lines().collect();
assert_eq!(
config.len(),
3,
"var `{}` format incorrect, expected 3 lines",
Self::SAME_PROCESS_VAR
);
ViewConfig {
version: Txt::from_str(config[0]),
server_name: Txt::from_str(config[1]),
headless: config[2] == "true",
}
}
/// Assert that the [`VERSION`] is the same in the app-process and view-process.
///
/// This method must be called in the view-process implementation, it fails if the versions don't match, panics if
/// `is_same_process` or writes to *stderr* and exits with code .
///
/// [`VERSION`]: crate::VERSION
pub fn assert_version(&self, is_same_process: bool) {
if self.version != crate::VERSION {
let msg = format!(
"view API version is not equal, app-process: {}, view-process: {}",
self.version,
crate::VERSION
);
if is_same_process {
panic!("{}", msg)
} else {
eprintln!("{msg}");
std::process::exit(i32::from_le_bytes(*b"vapi"));
}
}
}
/// Returns `true` if a view-process exited because of [`assert_version`].
///
/// [`assert_version`]: Self::assert_version
pub fn is_version_err(exit_code: Option<i32>, stderr: Option<&str>) -> bool {
exit_code.map(|e| e == i32::from_le_bytes(*b"vapi")).unwrap_or(false)
|| stderr.map(|s| s.contains("view API version is not equal")).unwrap_or(false)
}
/// Used to communicate the `ViewConfig` in the same process, we don't use
/// a static variable because prebuild view-process implementations don't
/// statically link with the same variable.
const SAME_PROCESS_VAR: &'static str = "zng_view_api::ViewConfig";
const SG_WAITING: &'static str = "WAITING";
const SG_DONE: &'static str = "DONE";
}