Repository: zng-ui/zng (language: rust)

Issue:
Refactor single_instance to use new zng::env::init!



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 688cffcbe..995f19e98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,14 +1,17 @@
 # Unpublished
 
-* Add `zng::env::on_process_start!` to inject a callback in `zng::env::init!`.
+* Add `zng::env::on_process_start!` to inject custom code in `zng::env::init!`.
 * Add `zng::env::on_process_exit` to register a handler for process exit.
 * Add `zng::env::exit` to collaboratively exit the process.
+* Add `zng::app::on_app_start` to register a handler to be called when the `APP` context starts.
 * **Breaking** Remove `zng_view::init`, `zng_view_prebuilt::init`, `zng::view_process::default::init`, `zng::view_process::prebuilt::init`.
     - Only `zng::env::init!()` needs to be called to setup the view-process.
 * **Breaking** `zng_view_api::Controller::start` now requires the exe path and supports optional env variables to set.
     - This only affects custom view-process implementers.
 * **Breaking** New default `zng::env::res`, was `./assets` now is `./res`.
 * **Breaking** Renamed `zng_view_prebuilt::ViewLib::init` to `view_process_main`.
+* **Breaking** Remove `is_single_instance`, `single_instance`, `single_instance_named`.
+    - Single instance now enabled automatically just by setting `feature="single_instance"`.
 
 # 0.6.2
 
diff --git a/crates/zng-app/src/running.rs b/crates/zng-app/src/running.rs
index d7ce7a47d..9daabad24 100644
--- a/crates/zng-app/src/running.rs
+++ b/crates/zng-app/src/running.rs
@@ -8,6 +8,7 @@ use std::{
 };
 
 use crate::Deadline;
+use parking_lot::Mutex;
 use zng_app_context::{app_local, AppScope};
 use zng_task::DEADLINE_APP;
 use zng_time::{InstantMode, INSTANT_APP};
@@ -97,6 +98,11 @@ impl<E: AppExtension> RunningApp<E> {
             extensions.init();
         }
 
+        let args = AppStartArgs { _private: () };
+        for h in zng_unique_id::hot_static_ref!(ON_APP_START).lock().iter_mut() {
+            h(&args)
+        }
+
         RunningApp {
             extensions: (process, extensions),
 
@@ -909,6 +915,27 @@ impl<E: AppExtension> Drop for RunningApp<E> {
     }
 }
 
+/// Arguments for [`on_app_start`] handlers.
+///
+/// Empty in this release. The handler is called in the new app context so you can use `APP` or
+/// any other app service to access the new app.
+pub struct AppStartArgs {
+    _private: (),
+}
+
+/// Register a `handler` to run when an `APP` starts running in the process.
+///
+/// The `handler` is called in the new app context, just before the "run" future executes, all app service are already available in it.
+///
+/// In `"multi_app"` builds the handler can be called more them once.
+pub fn on_app_start(handler: impl FnMut(&AppStartArgs) + Send + 'static) {
+    zng_unique_id::hot_static_ref!(ON_APP_START).lock().push(Box::new(handler))
+}
+zng_unique_id::hot_static! {
+    static ON_APP_START: Mutex<Vec<AppStartHandler>> = Mutex::new(vec![]);
+}
+type AppStartHandler = Box<dyn FnMut(&AppStartArgs) + Send + 'static>;
+
 /// App main loop timer.
 #[derive(Debug)]
 pub(crate) struct LoopTimer {
diff --git a/crates/zng-env/src/process.rs b/crates/zng-env/src/process.rs
index 35a49a60c..7cc53f579 100644
--- a/crates/zng-env/src/process.rs
+++ b/crates/zng-env/src/process.rs
@@ -35,6 +35,11 @@ use parking_lot::Mutex;
 /// ```
 ///
 /// Note the use of [`exit`], it is important to call it to collaborate with [`on_process_exit`] handlers.
+///
+/// # App Context
+///
+/// This event happens on the executable process context, before any `APP` context starts, you can use
+/// `zng::app::on_app_start` here to register a handler to be called in the app context, if and when it starts.
 #[macro_export]
 macro_rules! on_process_start {
     ($closure:expr) => {
@@ -91,7 +96,7 @@ pub(crate) fn process_init() -> impl Drop {
 
 /// Arguments for [`on_process_start`] handlers.
 ///
-/// Empty in this release
+/// Empty in this release.
 pub struct ProcessStartArgs {
     _private: (),
 }
diff --git a/crates/zng-ext-single-instance/src/lib.rs b/crates/zng-ext-single-instance/src/lib.rs
index 8e142f846..4298d4a7f 100644
--- a/crates/zng-ext-single-instance/src/lib.rs
+++ b/crates/zng-ext-single-instance/src/lib.rs
@@ -27,9 +27,6 @@ use zng_txt::{ToTxt, Txt};
 /// Events this extension provides.
 ///
 /// * [`APP_INSTANCE_EVENT`]
-///
-/// Note that the event will notify even if [`single_instance()`] is not called, but it must be
-/// called to make the app actually single instance.
 #[derive(Default)]
 pub struct SingleInstanceManager {}
 impl AppExtension for SingleInstanceManager {
@@ -136,44 +133,15 @@ event! {
     pub static APP_INSTANCE_EVENT: AppInstanceArgs;
 }
 
-/// Enable single instance mode for the app-process.
-///
-/// The current executable path is used as the unique name. See [`single_instance_named`] for more details about name.
-///
-/// # Examples
-///
-/// The example below demonstrates a single instance process setup. The [`single_instance()`] function is called before
-/// the app starts building. After the app starts building, before run, a subscription for [`APP_INSTANCE_EVENT`] is setup,
-/// this event will receive args for the current instance on run and for other instances latter.
-///
-pub fn single_instance() {
-    single_instance_named(
+zng_env::on_process_start!(|_| {
+    single_instance_impl(
         std::env::current_exe()
             .and_then(dunce::canonicalize)
             .expect("current exe is required")
             .display()
             .to_txt(),
     )
-}
-
-/// Enable single instance mode for the app-process, using a custom unique identifier for the app.
-///
-/// The name should have only ASCII alphanumerics and be at maximum 128 bytes in length only, otherwise it will be mangled.
-/// The name is used to create a global lock, see the [single-instance] crate for more details.
-///
-/// [single-instance]: https://docs.rs/single-instance/
-pub fn single_instance_named(unique_name: impl Into<Txt>) {
-    single_instance_impl(unique_name.into())
-}
-
-/// If single instance mode is enabled and the current process is the instance.
-///
-/// This is only valid after calling [`single_instance`] or [`single_instance_named`].
-///
-/// [`single_instance`]: fn@single_instance
-pub fn is_single_instance() -> bool {
-    SINGLE_INSTANCE.lock().is_some()
-}
+});
 
 fn single_instance_impl(name: Txt) {
     let mut lock = SINGLE_INSTANCE.lock();
diff --git a/crates/zng/Cargo.toml b/crates/zng/Cargo.toml
index 822e80390..010134c74 100644
--- a/crates/zng/Cargo.toml
+++ b/crates/zng/Cargo.toml
@@ -74,9 +74,10 @@ test_util = [
 # Not enabled by default, but enabled by `feature="test_util"`.
 multi_app = ["zng-app/multi_app"]
 
-# Allow single app-process instance mode.
+# Enables single app-process instance mode.
 #
-# Note that `zng::app::single_instance()` must be called to enable single instance mode.
+# Builds with this feature only allow one app-process, subsequent attempts to spawn the app redirect to
+# the running app-process.
 single_instance = ["dep:zng-ext-single-instance"]
 
 # Allow app-process crash handler.
diff --git a/crates/zng/README.md b/crates/zng/README.md
index 125fd9ad6..f857e2ecd 100644
--- a/crates/zng/README.md
+++ b/crates/zng/README.md
@@ -98,9 +98,10 @@ depending on the current thread.
 Not enabled by default, but enabled by `feature="test_util"`.
 
 #### `"single_instance"`
-Allow single app-process instance mode.
+Enables single app-process instance mode.
 
-Note that `zng::app::single_instance()` must be called to enable single instance mode.
+Builds with this feature only allow one app-process, subsequent attempts to spawn the app redirect to
+the running app-process.
 
 #### `"crash_handler"`
 Allow app-process crash handler.
diff --git a/crates/zng/src/app.rs b/crates/zng/src/app.rs
index 100562be2..704a7e7e5 100644
--- a/crates/zng/src/app.rs
+++ b/crates/zng/src/app.rs
@@ -403,8 +403,9 @@
 #[allow(deprecated)]
 pub use zng_app::about_app_from_crate;
 pub use zng_app::{
-    print_tracing, AboutApp, AppControlFlow, AppEventObserver, AppExtended, AppExtension, AppExtensionBoxed, AppExtensionInfo, DInstant,
-    Deadline, ExitRequestedArgs, HeadlessApp, InstantMode, EXIT_CMD, EXIT_REQUESTED_EVENT, INSTANT,
+    on_app_start, print_tracing, AboutApp, AppControlFlow, AppEventObserver, AppExtended, AppExtension, AppExtensionBoxed,
+    AppExtensionInfo, AppStartArgs, DInstant, Deadline, ExitRequestedArgs, HeadlessApp, InstantMode, EXIT_CMD, EXIT_REQUESTED_EVENT,
+    INSTANT,
 };
 
 #[cfg(feature = "test_util")]
@@ -430,7 +431,7 @@ pub mod raw_device_events {
 }
 
 #[cfg(feature = "single_instance")]
-pub use zng_ext_single_instance::{is_single_instance, single_instance, single_instance_named, AppInstanceArgs, APP_INSTANCE_EVENT};
+pub use zng_ext_single_instance::{AppInstanceArgs, APP_INSTANCE_EVENT};
 
 /// App-process crash handler.
 ///


Test patch: