Reduce LLVM IR bloat in closure funnels (#63426)
Follow-up to https://github.com/zed-industries/zed/pull/63232 Cuts closure monomorphization in settings and editor: settings file updates now share one boxed write-pipeline instantiation instead of inlining the `SettingsContent` clone, both `serde_json::to_value` calls, and the JSON diff at every call site; `Editor::register_action` and the settings enum dropdown get the same treatment. Behavior and public signatures are unchanged; thin generic shims remain. Symbol table of the release binary: Before: `update_settings_file_inner` 229 instantiations, inlined `serde_json` `SerializeMap` machinery 6547 copies After: 3 instantiations, 673 copies (-89.7%); `render_dropdown` is no longer instantiated at all With sccache disabled, `cargo clean` and `cargo build --release -p zed`: Before: zed binary 420677880 bytes After: zed binary 396999464 bytes (-5.6%, -23.7MB) Release Notes: - N/A
This commit is contained in:
1 parent
fa839e548d
commit
6dc2206180
5 files changed
+149
-71
No files matched your search
@@ -10809,15 +10809,26 @@ impl Editor {
|
||||
pub fn register_action<A: Action>(
|
||||
&mut self,
|
||||
listener: impl Fn(&A, &mut Window, &mut App) + 'static,
|
||||
) -> Subscription {
|
||||
self.register_action_erased(
|
||||
TypeId::of::<A>(),
|
||||
Arc::new(move |action, window, cx| {
|
||||
listener(action.downcast_ref().unwrap(), window, cx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn register_action_erased(
|
||||
&mut self,
|
||||
action_type: TypeId,
|
||||
listener: Arc<dyn Fn(&dyn Any, &mut Window, &mut App)>,
|
||||
) -> Subscription {
|
||||
let id = self.next_editor_action_id.post_inc();
|
||||
let listener = Arc::new(listener);
|
||||
self.editor_actions.borrow_mut().insert(
|
||||
id,
|
||||
Box::new(move |_, window, _| {
|
||||
let listener = listener.clone();
|
||||
window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
|
||||
let action = action.downcast_ref().unwrap();
|
||||
window.on_action(action_type, move |action, phase, window, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
listener(action, window, cx)
|
||||
}
|
||||
|
||||
@@ -10678,10 +10678,25 @@ pub fn register_action<T: Action>(
|
||||
editor: &Entity<Editor>,
|
||||
window: &mut Window,
|
||||
listener: impl Fn(&mut Editor, &T, &mut Window, &mut Context<Editor>) + 'static,
|
||||
) {
|
||||
register_action_erased(
|
||||
editor,
|
||||
window,
|
||||
TypeId::of::<T>(),
|
||||
Box::new(move |editor, action, window, cx| {
|
||||
listener(editor, action.downcast_ref().unwrap(), window, cx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn register_action_erased(
|
||||
editor: &Entity<Editor>,
|
||||
window: &mut Window,
|
||||
action_type: TypeId,
|
||||
listener: Box<dyn Fn(&mut Editor, &dyn std::any::Any, &mut Window, &mut Context<Editor>)>,
|
||||
) {
|
||||
let editor = editor.clone();
|
||||
window.on_action(TypeId::of::<T>(), move |action, phase, window, cx| {
|
||||
let action = action.downcast_ref().unwrap();
|
||||
window.on_action(action_type, move |action, phase, window, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
editor.update(cx, |editor, cx| {
|
||||
listener(editor, action, window, cx);
|
||||
|
||||
@@ -559,7 +559,7 @@ impl SettingsStore {
|
||||
fn update_settings_file_inner(
|
||||
&self,
|
||||
fs: Arc<dyn Fs>,
|
||||
update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
|
||||
update: Box<dyn Send + FnOnce(String, AsyncApp) -> Result<String>>,
|
||||
) -> oneshot::Receiver<Result<()>> {
|
||||
let (tx, rx) = oneshot::channel::<Result<()>>();
|
||||
self.setting_file_updates_tx
|
||||
@@ -626,11 +626,17 @@ impl SettingsStore {
|
||||
fs: Arc<dyn Fs>,
|
||||
update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
|
||||
) -> oneshot::Receiver<Result<()>> {
|
||||
self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
|
||||
cx.read_global(|store: &SettingsStore, cx| {
|
||||
store.new_text_for_update(old_text, |content| update(content, cx))
|
||||
})
|
||||
})
|
||||
let mut update = Some(update);
|
||||
self.update_settings_file_inner(
|
||||
fs,
|
||||
Box::new(move |old_text: String, cx: AsyncApp| {
|
||||
cx.read_global(|store: &SettingsStore, cx| {
|
||||
store.new_text_for_update_inner(old_text, &mut |content| {
|
||||
(update.take().expect("called once"))(content, cx)
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn import_vscode_settings(
|
||||
@@ -638,11 +644,14 @@ impl SettingsStore {
|
||||
fs: Arc<dyn Fs>,
|
||||
vscode_settings: VsCodeSettings,
|
||||
) -> oneshot::Receiver<Result<()>> {
|
||||
self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
|
||||
cx.read_global(|store: &SettingsStore, _cx| {
|
||||
store.get_vscode_edits(old_text, &vscode_settings)
|
||||
})
|
||||
})
|
||||
self.update_settings_file_inner(
|
||||
fs,
|
||||
Box::new(move |old_text: String, cx: AsyncApp| {
|
||||
cx.read_global(|store: &SettingsStore, _cx| {
|
||||
store.get_vscode_edits(old_text, &vscode_settings)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_all_files(&self) -> Vec<SettingsFile> {
|
||||
@@ -833,7 +842,18 @@ impl SettingsStore {
|
||||
old_text: String,
|
||||
update: impl FnOnce(&mut SettingsContent),
|
||||
) -> Result<String> {
|
||||
let edits = self.edits_for_update(&old_text, update)?;
|
||||
let mut update = Some(update);
|
||||
self.new_text_for_update_inner(old_text, &mut |content| {
|
||||
(update.take().expect("called once"))(content)
|
||||
})
|
||||
}
|
||||
|
||||
fn new_text_for_update_inner(
|
||||
&self,
|
||||
old_text: String,
|
||||
update: &mut dyn FnMut(&mut SettingsContent),
|
||||
) -> Result<String> {
|
||||
let edits = self.edits_for_update_inner(&old_text, update)?;
|
||||
let mut new_text = old_text;
|
||||
for (range, replacement) in edits.into_iter() {
|
||||
new_text.replace_range(range, &replacement);
|
||||
@@ -853,6 +873,17 @@ impl SettingsStore {
|
||||
&self,
|
||||
text: &str,
|
||||
update: impl FnOnce(&mut SettingsContent),
|
||||
) -> Result<Vec<(Range<usize>, String)>> {
|
||||
let mut update = Some(update);
|
||||
self.edits_for_update_inner(text, &mut |content| {
|
||||
(update.take().expect("called once"))(content)
|
||||
})
|
||||
}
|
||||
|
||||
fn edits_for_update_inner(
|
||||
&self,
|
||||
text: &str,
|
||||
update: &mut dyn FnMut(&mut SettingsContent),
|
||||
) -> Result<Vec<(Range<usize>, String)>> {
|
||||
let old_content = if text.trim().is_empty() {
|
||||
UserSettingsContent::default()
|
||||
|
||||
@@ -8,37 +8,47 @@ use ui::{
|
||||
};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct EnumVariantDropdown<T>
|
||||
where
|
||||
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
|
||||
{
|
||||
pub struct EnumVariantDropdown {
|
||||
id: ElementId,
|
||||
current_value: T,
|
||||
variants: &'static [T],
|
||||
selected_index: usize,
|
||||
labels: &'static [&'static str],
|
||||
should_do_title_case: bool,
|
||||
tab_index: Option<isize>,
|
||||
disabled: bool,
|
||||
aria_label: Option<SharedString>,
|
||||
aria_description: Option<SharedString>,
|
||||
on_change: Rc<dyn Fn(T, &mut ui::Window, &mut App) + 'static>,
|
||||
on_change: Rc<dyn Fn(usize, &mut ui::Window, &mut App) + 'static>,
|
||||
}
|
||||
|
||||
impl<T> EnumVariantDropdown<T>
|
||||
where
|
||||
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
impl EnumVariantDropdown {
|
||||
pub fn new<T>(
|
||||
id: impl Into<ElementId>,
|
||||
current_value: T,
|
||||
variants: &'static [T],
|
||||
labels: &'static [&'static str],
|
||||
on_change: impl Fn(T, &mut ui::Window, &mut App) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
|
||||
{
|
||||
let selected_index = variants
|
||||
.iter()
|
||||
.position(|v| *v == current_value)
|
||||
.unwrap_or(0);
|
||||
Self::new_indexed(id, selected_index, labels, move |index, window, cx| {
|
||||
on_change(variants[index], window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_indexed(
|
||||
id: impl Into<ElementId>,
|
||||
selected_index: usize,
|
||||
labels: &'static [&'static str],
|
||||
on_change: impl Fn(usize, &mut ui::Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
current_value,
|
||||
variants,
|
||||
selected_index,
|
||||
labels,
|
||||
should_do_title_case: true,
|
||||
tab_index: None,
|
||||
@@ -79,33 +89,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RenderOnce for EnumVariantDropdown<T>
|
||||
where
|
||||
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
|
||||
{
|
||||
impl RenderOnce for EnumVariantDropdown {
|
||||
fn render(self, window: &mut ui::Window, cx: &mut ui::App) -> impl gpui::IntoElement {
|
||||
let current_value_label = self.labels[self
|
||||
.variants
|
||||
.iter()
|
||||
.position(|v| *v == self.current_value)
|
||||
.unwrap()];
|
||||
let current_value_label = self.labels[self.selected_index];
|
||||
|
||||
let context_menu = window.use_keyed_state(current_value_label, cx, |window, cx| {
|
||||
ContextMenu::new(window, cx, move |mut menu, _, _| {
|
||||
for (&value, &label) in std::iter::zip(self.variants, self.labels) {
|
||||
for (index, &label) in self.labels.iter().enumerate() {
|
||||
let on_change = self.on_change.clone();
|
||||
let current_value = self.current_value;
|
||||
menu = menu.toggleable_entry(
|
||||
if self.should_do_title_case {
|
||||
label.to_title_case()
|
||||
} else {
|
||||
label.to_string()
|
||||
},
|
||||
value == current_value,
|
||||
index == self.selected_index,
|
||||
IconPosition::End,
|
||||
None,
|
||||
move |window, cx| {
|
||||
on_change(value, window, cx);
|
||||
on_change(index, window, cx);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -354,33 +354,52 @@ impl SettingFieldRenderer {
|
||||
) -> Stateful<Div>
|
||||
+ 'static,
|
||||
) -> &mut Self {
|
||||
let key = TypeId::of::<T>();
|
||||
let renderer = Box::new(
|
||||
move |settings_window: &SettingsWindow,
|
||||
item: &SettingItem,
|
||||
settings_file: SettingsUiFile,
|
||||
metadata: Option<&SettingsFieldMetadata>,
|
||||
sub_field: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<SettingsWindow>| {
|
||||
let field = *item
|
||||
.field
|
||||
.as_ref()
|
||||
.as_any()
|
||||
.downcast_ref::<SettingField<T>>()
|
||||
.unwrap();
|
||||
renderer(
|
||||
settings_window,
|
||||
item,
|
||||
field,
|
||||
settings_file,
|
||||
metadata,
|
||||
sub_field,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
);
|
||||
self.add_renderer_erased(
|
||||
TypeId::of::<T>(),
|
||||
Box::new(
|
||||
move |settings_window: &SettingsWindow,
|
||||
item: &SettingItem,
|
||||
settings_file: SettingsUiFile,
|
||||
metadata: Option<&SettingsFieldMetadata>,
|
||||
sub_field: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<SettingsWindow>| {
|
||||
let field = *item
|
||||
.field
|
||||
.as_ref()
|
||||
.as_any()
|
||||
.downcast_ref::<SettingField<T>>()
|
||||
.unwrap();
|
||||
renderer(
|
||||
settings_window,
|
||||
item,
|
||||
field,
|
||||
settings_file,
|
||||
metadata,
|
||||
sub_field,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_renderer_erased(
|
||||
&mut self,
|
||||
key: TypeId,
|
||||
renderer: Box<
|
||||
dyn Fn(
|
||||
&SettingsWindow,
|
||||
&SettingItem,
|
||||
SettingsUiFile,
|
||||
Option<&SettingsFieldMetadata>,
|
||||
bool,
|
||||
&mut Window,
|
||||
&mut Context<SettingsWindow>,
|
||||
) -> Stateful<Div>,
|
||||
>,
|
||||
) -> &mut Self {
|
||||
self.renderers.borrow_mut().insert(key, renderer);
|
||||
self
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user