Skip to content

Commit 746409c

Browse files
authored
Remove one shot commands. (#959)
1 parent 9aaa883 commit 746409c

File tree

6 files changed

+59
-81
lines changed

6 files changed

+59
-81
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
6464
- `AppDelegate::command` now receives a `Target` instead of a `&Target`. ([#909] by [@xStrom])
6565
- `SHOW_WINDOW` and `CLOSE_WINDOW` commands now only use `Target` to determine the affected window. ([#928] by [@finnerale])
6666
- Replaced `NEW_WINDOW`, `SET_MENU` and `SHOW_CONTEXT_MENU` commands with methods on `EventCtx` and `DelegateCtx`. ([#931] by [@finnerale])
67+
- Replaced `Command::one_shot` and `::take_object` with a `SingleUse` payload wrapper type. ([#959] by [@finnerale])
6768

6869
### Deprecated
6970

@@ -198,6 +199,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
198199
[#951]: https://github.com/xi-editor/druid/pull/951
199200
[#953]: https://github.com/xi-editor/druid/pull/953
200201
[#954]: https://github.com/xi-editor/druid/pull/954
202+
[#959]: https://github.com/xi-editor/druid/pull/959
201203

202204
## [0.5.0] - 2020-04-01
203205

druid/src/app_delegate.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use std::{
1919
collections::VecDeque,
2020
};
2121

22-
use crate::{commands, Command, Data, Env, Event, MenuDesc, Target, WindowDesc, WindowId};
22+
use crate::{
23+
commands, Command, Data, Env, Event, MenuDesc, SingleUse, Target, WindowDesc, WindowId,
24+
};
2325

2426
/// A context passed in to [`AppDelegate`] functions.
2527
///
@@ -55,7 +57,7 @@ impl<'a> DelegateCtx<'a> {
5557
pub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>) {
5658
if self.app_data_type == TypeId::of::<T>() {
5759
self.submit_command(
58-
Command::one_shot(commands::NEW_WINDOW, desc),
60+
Command::new(commands::NEW_WINDOW, SingleUse::new(desc)),
5961
Target::Global,
6062
);
6163
} else {

druid/src/command.rs

Lines changed: 43 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,8 @@ pub struct Selector(&'static str);
3434
/// A `Command` consists of a [`Selector`], that indicates what the command is,
3535
/// and an optional argument, that can be used to pass arbitrary data.
3636
///
37-
///
38-
/// # One-shot and reusable `Commands`
39-
///
40-
/// Commands come in two varieties, 'reusable' and 'one-shot'.
41-
///
42-
/// Regular commands are created with [`Command::new`], and their argument
43-
/// objects may be accessed repeatedly, via [`Command::get_object`].
44-
///
45-
/// One-shot commands are intended for cases where an object should only be
46-
/// used once; an example would be if you have some resource that cannot be
47-
/// cloned, and you wish to send it to another widget.
37+
/// If the payload can't or shouldn't be cloned,
38+
/// wrapping it with [`SingleUse`] allows you to `take` the object.
4839
///
4940
/// # Examples
5041
/// ```
@@ -59,31 +50,50 @@ pub struct Selector(&'static str);
5950
///
6051
/// [`Command::new`]: #method.new
6152
/// [`Command::get_object`]: #method.get_object
53+
/// [`SingleUse`]: struct.SingleUse.html
6254
/// [`Selector`]: struct.Selector.html
6355
#[derive(Debug, Clone)]
6456
pub struct Command {
6557
/// The command's `Selector`.
6658
pub selector: Selector,
67-
object: Option<Arg>,
59+
object: Option<Arc<dyn Any>>,
6860
}
6961

70-
#[derive(Debug, Clone)]
71-
enum Arg {
72-
Reusable(Arc<dyn Any>),
73-
OneShot(Arc<Mutex<Option<Box<dyn Any>>>>),
74-
}
62+
/// A wrapper type for [`Command`] arguments that should only be used once.
63+
///
64+
/// This is useful if you have some resource that cannot be
65+
/// cloned, and you wish to send it to another widget.
66+
///
67+
/// # Examples
68+
/// ```
69+
/// use druid::{Command, Selector, SingleUse};
70+
///
71+
/// struct CantClone(u8);
72+
///
73+
/// let selector = Selector::new("use-once");
74+
/// let num = CantClone(42);
75+
/// let command = Command::new(selector, SingleUse::new(num));
76+
///
77+
/// let object: &SingleUse<CantClone> = command.get_object().unwrap();
78+
/// if let Some(num) = object.take() {
79+
/// // now you own the data
80+
/// assert_eq!(num.0, 42);
81+
/// }
82+
///
83+
/// // subsequent calls will return `None`
84+
/// assert!(object.take().is_none());
85+
/// ```
86+
///
87+
/// [`Command`]: struct.Command.html
88+
pub struct SingleUse<T>(Mutex<Option<T>>);
7589

7690
/// Errors that can occur when attempting to retrieve the a command's argument.
7791
#[derive(Debug, Clone, PartialEq)]
7892
pub enum ArgumentError {
7993
/// The command did not have an argument.
8094
NoArgument,
81-
/// The argument was expected to be reusable and wasn't, or vice-versa.
82-
WrongVariant,
8395
/// The argument could not be downcast to the specified type.
8496
IncorrectType,
85-
/// The one-shot argument has already been taken.
86-
Consumed,
8797
}
8898

8999
/// The target of a command.
@@ -231,67 +241,34 @@ impl Command {
231241
pub fn new(selector: Selector, arg: impl Any) -> Self {
232242
Command {
233243
selector,
234-
object: Some(Arg::Reusable(Arc::new(arg))),
235-
}
236-
}
237-
238-
/// Create a new 'one-shot' `Command`.
239-
///
240-
/// Unlike those created with `Command::new`, one-shot commands cannot
241-
/// be reused; their argument is consumed when it is accessed, via
242-
/// [`take_object`].
243-
///
244-
/// [`take_object`]: #method.take_object
245-
pub fn one_shot(selector: Selector, arg: impl Any) -> Self {
246-
Command {
247-
selector,
248-
object: Some(Arg::OneShot(Arc::new(Mutex::new(Some(Box::new(arg)))))),
244+
object: Some(Arc::new(arg)),
249245
}
250246
}
251247

252248
/// Used to create a command from the types sent via an `ExtEventSink`.
253249
pub(crate) fn from_ext(selector: Selector, object: Option<Box<dyn Any + Send>>) -> Self {
254250
let object: Option<Box<dyn Any>> = object.map(|obj| obj as Box<dyn Any>);
255-
let object = object.map(|o| Arg::Reusable(o.into()));
251+
let object = object.map(|o| o.into());
256252
Command { selector, object }
257253
}
258254

259255
/// Return a reference to this `Command`'s object, if it has one.
260-
///
261-
/// This only works for 'reusable' commands; it does not work for commands
262-
/// created with [`one_shot`].
263-
///
264-
/// [`one_shot`]: #method.one_shot
265256
pub fn get_object<T: Any>(&self) -> Result<&T, ArgumentError> {
266257
match self.object.as_ref() {
267-
Some(Arg::Reusable(o)) => o.downcast_ref().ok_or(ArgumentError::IncorrectType),
268-
Some(Arg::OneShot(_)) => Err(ArgumentError::WrongVariant),
258+
Some(o) => o.downcast_ref().ok_or(ArgumentError::IncorrectType),
269259
None => Err(ArgumentError::NoArgument),
270260
}
271261
}
262+
}
272263

273-
/// Attempt to take the object of a [`one-shot`] command.
274-
///
275-
/// [`one-shot`]: #method.one_shot
276-
pub fn take_object<T: Any>(&self) -> Result<Box<T>, ArgumentError> {
277-
match self.object.as_ref() {
278-
Some(Arg::Reusable(_)) => Err(ArgumentError::WrongVariant),
279-
Some(Arg::OneShot(inner)) => {
280-
let obj = inner
281-
.lock()
282-
.unwrap()
283-
.take()
284-
.ok_or(ArgumentError::Consumed)?;
285-
match obj.downcast::<T>() {
286-
Ok(obj) => Ok(obj),
287-
Err(obj) => {
288-
inner.lock().unwrap().replace(obj);
289-
Err(ArgumentError::IncorrectType)
290-
}
291-
}
292-
}
293-
None => Err(ArgumentError::NoArgument),
294-
}
264+
impl<T: Any> SingleUse<T> {
265+
pub fn new(data: T) -> Self {
266+
SingleUse(Mutex::new(Some(data)))
267+
}
268+
269+
/// Takes the value, leaving a None in its place.
270+
pub fn take(&self) -> Option<T> {
271+
self.0.lock().unwrap().take()
295272
}
296273
}
297274

@@ -315,12 +292,6 @@ impl std::fmt::Display for ArgumentError {
315292
match self {
316293
ArgumentError::NoArgument => write!(f, "Command has no argument"),
317294
ArgumentError::IncorrectType => write!(f, "Downcast failed: wrong concrete type"),
318-
ArgumentError::Consumed => write!(f, "One-shot command arguemnt already consumed"),
319-
ArgumentError::WrongVariant => write!(
320-
f,
321-
"Incorrect access method for argument type; \
322-
check Command::one_shot docs for more detail."
323-
),
324295
}
325296
}
326297
}

druid/src/contexts.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ use crate::core::{BaseState, CommandQueue, FocusChange};
2424
use crate::piet::Piet;
2525
use crate::piet::RenderContext;
2626
use crate::{
27-
commands, Affine, Command, ContextMenu, Cursor, Insets, MenuDesc, Point, Rect, Size, Target,
28-
Text, TimerToken, Vec2, WidgetId, WindowDesc, WindowHandle, WindowId,
27+
commands, Affine, Command, ContextMenu, Cursor, Insets, MenuDesc, Point, Rect, SingleUse, Size,
28+
Target, Text, TimerToken, Vec2, WidgetId, WindowDesc, WindowHandle, WindowId,
2929
};
3030

3131
/// A mutable context provided to event handling methods of widgets.
@@ -255,7 +255,7 @@ impl<'a> EventCtx<'a> {
255255
pub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>) {
256256
if self.app_data_type == TypeId::of::<T>() {
257257
self.submit_command(
258-
Command::one_shot(commands::NEW_WINDOW, desc),
258+
Command::new(commands::NEW_WINDOW, SingleUse::new(desc)),
259259
Target::Global,
260260
);
261261
} else {

druid/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ pub use crate::core::WidgetPod;
153153
pub use app::{AppLauncher, WindowDesc};
154154
pub use app_delegate::{AppDelegate, DelegateCtx};
155155
pub use box_constraints::BoxConstraints;
156-
pub use command::{sys as commands, Command, Selector, Target};
156+
pub use command::{sys as commands, Command, Selector, SingleUse, Target};
157157
pub use contexts::{EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, Region, UpdateCtx};
158158
pub use data::Data;
159159
pub use env::{Env, Key, KeyOrValue, Value, ValueType};

druid/src/win_handler.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ use crate::ext_event::ExtEventHost;
3131
use crate::menu::ContextMenu;
3232
use crate::window::Window;
3333
use crate::{
34-
Command, Data, Env, Event, InternalEvent, KeyEvent, MenuDesc, Target, TimerToken, WindowDesc,
35-
WindowId,
34+
Command, Data, Env, Event, InternalEvent, KeyEvent, MenuDesc, SingleUse, Target, TimerToken,
35+
WindowDesc, WindowId,
3636
};
3737

3838
use crate::command::sys as sys_cmd;
@@ -591,7 +591,10 @@ impl<T: Data> AppState<T> {
591591
}
592592

593593
fn new_window(&mut self, cmd: Command) -> Result<(), Box<dyn std::error::Error>> {
594-
let desc = cmd.take_object::<WindowDesc<T>>()?;
594+
let desc = cmd.get_object::<SingleUse<WindowDesc<T>>>()?;
595+
// The NEW_WINDOW command is private and only druid can receive it by normal means,
596+
// thus unwrapping can be considered safe and deserves a panic.
597+
let desc = desc.take().unwrap();
595598
let window = desc.build_native(self)?;
596599
window.show();
597600
Ok(())

0 commit comments

Comments
 (0)