Reduce model memory usage

This commit is contained in:
Adrian Woźniak 2021-10-15 14:40:51 +02:00
parent 5abcfb99a6
commit d49c92cd53
No known key found for this signature in database
GPG Key ID: DE43476F72AD3F6C
12 changed files with 218 additions and 195 deletions

View File

@ -258,7 +258,7 @@ pub struct Issue {
pub struct IssueStatus {
pub id: IssueStatusId,
pub name: String,
pub position: ProjectId,
pub position: ListPosition,
pub project_id: ProjectId,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,

View File

@ -11,7 +11,7 @@ pub fn epic_field<Modal>(model: &Model, modal: &Modal, field_id: FieldId) -> Opt
where
Modal: IssueModal,
{
if model.epics.is_empty() {
if model.epic_ids.is_empty() {
return None;
}
let input = StyledSelect {
@ -19,10 +19,16 @@ where
name: "epic",
selected: vec![modal
.epic_id_value()
.and_then(|id| model.epics.iter().find(|epic| epic.id == id as EpicId))
.and_then(|id| model.epics_by_id.get(&(id as EpicId)))
.map(epic_select_option)
.unwrap_or_default()],
options: Some(model.epics.iter().map(epic_select_option)),
options: Some(
model
.epic_ids
.iter()
.filter_map(|id| model.epics_by_id.get(id))
.map(epic_select_option),
),
variant: SelectVariant::Normal,
clearable: true,
text_filter: modal.epic_state().text_filter.as_str(),

View File

@ -339,15 +339,13 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
comment_id,
)) => {
let id = *comment_id;
let body = model
.comments
.iter()
.find(|c| c.id == id)
.comments_by_id
.get(comment_id)
.map(|c| c.body.clone())
.unwrap_or_default();
modal.comment_form.body = body;
modal.comment_form.id = Some(id);
modal.comment_form.id = Some(*comment_id);
modal.comment_form.creating = true;
}
Msg::DeleteComment(comment_id) => {

View File

@ -243,7 +243,11 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
vec![div![C!["fakeTextArea"], "Add a comment...", handler]]
};
let comments = model.comments.iter().flat_map(|c| comment(model, modal, c));
let comments = model
.comment_ids
.iter()
.flat_map(|id| model.comments_by_id.get(id))
.flat_map(|c| comment(model, modal, c));
div![
C!["left"],
@ -403,11 +407,22 @@ fn status_select(
opened: status_state.opened,
variant: SelectVariant::Normal,
text_filter: status_state.text_filter.as_str(),
options: Some(model.issue_statuses.iter().map(issue_status_select_option)),
selected: model
.issue_statuses
options: Some(
model
.issue_status_ids
.iter()
.filter_map(|id| model.issue_statuses_by_id.get(id))
.map(issue_status_select_option),
),
selected: model
.issue_status_ids
.iter()
.filter_map(|id| {
model
.issue_statuses_by_id
.get(id)
.filter(|is| is.id == payload.issue_status_id)
})
.map(issue_status_select_option)
.collect(),
@ -424,7 +439,7 @@ fn status_select(
}
#[inline(always)]
fn issue_status_select_option<'l>(is: &'l IssueStatus) -> StyledSelectOption<'l> {
fn issue_status_select_option(is: &IssueStatus) -> StyledSelectOption<'_> {
StyledSelectOption {
value: is.id as u32,
class_list: is.name.as_str(),

View File

@ -254,11 +254,11 @@ pub struct Model {
pub user_settings: Option<UserSetting>,
// comments
pub comments: Vec<Comment>,
pub comment_ids: Vec<CommentId>,
pub comments_by_id: HashMap<CommentId, Comment>,
// issue_statuses
pub issue_statuses: Vec<IssueStatus>,
pub issue_status_ids: Vec<IssueStatusId>,
pub issue_statuses_by_id: HashMap<IssueStatusId, IssueStatus>,
pub issue_statuses_by_name: HashMap<String, IssueStatus>,
@ -273,7 +273,7 @@ pub struct Model {
pub projects: Vec<Project>,
// epics
pub epics: Vec<Epic>,
pub epic_ids: Vec<EpicId>,
pub epics_by_id: HashMap<EpicId, Epic>,
pub key_triggers: std::rc::Rc<std::cell::RefCell<HashMap<char, Box<dyn BuildMsg>>>>,
@ -304,15 +304,15 @@ impl Model {
user_ids: vec![],
users_by_id: HashMap::with_capacity(1_000),
user_settings: None,
comments: vec![],
comment_ids: vec![],
comments_by_id: HashMap::with_capacity(1_000),
issue_statuses: vec![],
issue_status_ids: vec![],
issue_statuses_by_id: HashMap::with_capacity(1_000),
issue_statuses_by_name: HashMap::with_capacity(1_000),
messages: vec![],
user_projects: vec![],
projects: vec![],
epics: vec![],
epic_ids: vec![],
issues_by_id: HashMap::with_capacity(1_000),
show_extras: false,
epics_by_id: HashMap::with_capacity(1_000),
@ -323,16 +323,6 @@ impl Model {
}
}
#[inline(always)]
pub fn issue_statuses(&self) -> &[IssueStatus] {
&self.issue_statuses
}
#[inline(always)]
pub fn epics(&self) -> &[Epic] {
&self.epics
}
#[inline(always)]
pub fn user(&self) -> &Option<User> {
&self.user

View File

@ -11,7 +11,11 @@ use crate::Msg;
pub fn view(model: &Model) -> Node<Msg> {
let page = crate::match_page!(model, Epics; Empty);
let epics = model.epics.iter().map(|epic| {
let epics = model
.epic_ids
.iter()
.filter_map(|id| model.epics_by_id.get(id))
.map(|epic| {
let issues = page.issues(epic.id).map(|v| {
v.iter()
.filter_map(|i| model.issues_by_id.get(i))

View File

@ -29,24 +29,26 @@ pub struct ProjectPage {
}
impl ProjectPage {
pub fn visible_issues<'issue, IssueStream>(
pub fn visible_issues<'model, IssueStream, IssueStatusStream, EpicStream>(
page: &ProjectPage,
epics: &[Epic],
statuses: &[IssueStatus],
num_of_epics: usize,
epics: EpicStream,
statuses: IssueStatusStream,
issues: IssueStream,
user: &Option<User>,
) -> Vec<EpicIssuePerStatus>
where
IssueStream: std::iter::Iterator<Item = &'issue Issue>,
IssueStream: std::iter::Iterator<Item = &'model Issue>,
IssueStatusStream: std::iter::Iterator<Item = &'model IssueStatus>,
EpicStream: std::iter::Iterator<Item = &'model Epic>,
{
let num_of_epics = epics.len();
let epics = vec![None].into_iter().chain(
epics
.iter()
.map(|epic| Some((epic.id, epic.name.as_str(), epic.starts_at, epic.ends_at))),
epics.map(|epic| Some((epic.id, epic.name.as_str(), epic.starts_at, epic.ends_at))),
);
let statuses = statuses.iter().map(|s| (s.id, s.name.as_str()));
let statuses = statuses
.map(|s| (s.id, s.name.as_str()))
.collect::<Vec<(IssueStatusId, &str)>>();
let issues = issues.filter(|issue| {
issue_filter_with_avatars(issue, &page.active_avatar_filters)
&& issue_filter_with_text(issue, page.text_filter.as_str())
@ -93,15 +95,15 @@ impl ProjectPage {
..Default::default()
};
for (current_status_id, issue_status_name) in statuses.to_owned() {
for (current_status_id, issue_status_name) in statuses.iter() {
let per_status_map = StatusIssueIds {
status_id: current_status_id,
status_id: *current_status_id,
status_name: issue_status_name.to_string(),
issue_ids: issues_per_epic_id
.get(&epic.map(|(id, ..)| id))
.map(|v| {
v.iter()
.filter(|issue| issue_filter_status(issue, current_status_id))
.filter(|issue| issue_filter_status(issue, *current_status_id))
.map(|issue| issue.id)
.collect()
})

View File

@ -7,7 +7,7 @@ use seed::prelude::Orders;
use crate::components::styled_select::StyledSelectChanged;
use crate::model::{Model, Page, PageContent};
use crate::pages::project_settings_page::ProjectSettingsPage;
use crate::ws::{board_load, send_ws_msg};
use crate::ws::{board_load, send_ws_msg, sort_issue_statuses};
use crate::{match_page_mut, FieldId, Msg, PageChanged, ProjectPageChange, WebSocketChanged};
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
@ -114,7 +114,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
))) => page.column_drag.clear_last(),
Msg::PageChanged(PageChanged::ProjectSettings(
ProjectPageChange::ColumnExchangePosition(issue_bellow_id),
)) => exchange_position(issue_bellow_id, model),
)) => swap_position(issue_bellow_id, model),
Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDropZone(
_issue_status_id,
))) => {
@ -124,12 +124,11 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
id,
))) => {
if page.edit_column_id.is_some() && id.is_none() {
let old_id = page.edit_column_id.as_ref().cloned();
if let Some(old_id) = page.edit_column_id {
let name = page.name.value.clone();
if let Some((id, pos)) = model
.issue_statuses
.iter()
.find(|is| Some(is.id) == old_id)
.issue_statuses_by_id
.get(&old_id)
.map(|is| (is.id, is.position))
{
send_ws_msg(
@ -139,24 +138,21 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
);
}
}
page.name.value = model
.issue_statuses
.iter()
.find_map(|is| {
if Some(is.id) == id {
Some(is.name.clone())
} else {
None
}
})
if let Some(id) = id {
page.name.value = model
.issue_statuses_by_id
.get(&id)
.map(|is| is.name.clone())
.unwrap_or_default();
}
page.edit_column_id = id;
}
Msg::PageChanged(PageChanged::ProjectSettings(
ProjectPageChange::SubmitIssueStatusForm,
)) => {
let name = page.name.value.clone();
let position = model.issue_statuses.len();
let position = model.issue_status_ids.len();
let ws_msg = WsMsgIssueStatus::IssueStatusCreate(name, position as i32).into();
send_ws_msg(ws_msg, model.ws.as_ref(), orders);
}
@ -164,11 +160,8 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
}
}
fn exchange_position(bellow_id: IssueStatusId, model: &mut Model) {
let page = match &mut model.page_content {
PageContent::ProjectSettings(page) => page,
_ => return,
};
fn swap_position(bellow_id: IssueStatusId, model: &mut Model) {
let page = crate::match_page_mut!(model, ProjectSettings);
if page.column_drag.dragged_or_last(bellow_id) {
return;
}
@ -177,41 +170,29 @@ fn exchange_position(bellow_id: IssueStatusId, model: &mut Model) {
_ => return log::error!("Nothing is dragged"),
};
let mut below = None;
let mut dragged = None;
let mut issues_statuses = vec![];
std::mem::swap(&mut issues_statuses, &mut model.issue_statuses);
let bellow = model
.issue_statuses_by_id
.get(&bellow_id)
.map(|is| is.position)
.unwrap_or_default();
let dragged = model
.issue_statuses_by_id
.get(&dragged_id)
.map(|is| is.position)
.unwrap_or_default();
for issue_status in issues_statuses.into_iter() {
match issue_status.id {
id if id == bellow_id => below = Some(issue_status),
id if id == dragged_id => dragged = Some(issue_status),
_ => model.issue_statuses.push(issue_status),
};
if let Some(is) = model.issue_statuses_by_id.get_mut(&dragged_id) {
is.position = bellow;
}
if let Some(is) = model.issue_statuses_by_id.get_mut(&bellow_id) {
is.position = dragged;
}
let mut below = match below {
Some(below) => below,
_ => return,
};
let mut dragged = match dragged {
Some(issue_status) => issue_status,
_ => {
model.issue_statuses.push(below);
return;
}
};
std::mem::swap(&mut dragged.position, &mut below.position);
page.column_drag.mark_dirty(dragged_id);
page.column_drag.mark_dirty(bellow_id);
page.column_drag.mark_dirty(dragged.id);
page.column_drag.mark_dirty(below.id);
model.issue_statuses.push(below);
model.issue_statuses.push(dragged);
model
.issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position));
page.column_drag.last_id = Some(bellow_id);
sort_issue_statuses(model);
}
fn sync(model: &mut Model, orders: &mut impl Orders<Msg>) {
@ -224,8 +205,7 @@ fn sync(model: &mut Model, orders: &mut impl Orders<Msg>) {
_ => return log::error!("bad content type"),
};
for id in dirty {
let IssueStatus { name, position, .. } =
match model.issue_statuses.iter().find(|is| is.id == id) {
let IssueStatus { name, position, .. } = match model.issue_statuses_by_id.get(&id) {
Some(is) => is,
_ => continue,
};

View File

@ -203,7 +203,7 @@ fn category_select_option<'l>(pc: ProjectCategory) -> StyledSelectOption<'l> {
/// Build draggable columns preview with option to remove and add new columns
#[inline(always)]
fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
let width = 100f64 / (model.issue_statuses.len() + 1) as f64;
let width = 100f64 / (model.issue_status_ids.len() + 1) as f64;
let column_style = format!("width: calc({width}% - 10px)", width = width);
let per_column_issue_count = model
.issue_ids
@ -216,11 +216,11 @@ fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
h
},
);
let columns: Vec<Node<Msg>> = model
.issue_statuses
let columns = model
.issue_status_ids
.iter()
.map(|is| column_preview(is, page, &per_column_issue_count, column_style.as_str()))
.collect();
.filter_map(|id| model.issue_statuses_by_id.get(id))
.map(|is| column_preview(is, page, &per_column_issue_count, column_style.as_str()));
let columns_section = section![
C!["columnsSection"],

View File

@ -81,7 +81,7 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
},
);
let issue_nav = if model.issue_statuses.is_empty() {
let issue_nav = if model.issue_status_ids.is_empty() {
vec![]
} else {
vec![

View File

@ -168,8 +168,15 @@ pub fn change_status(status_id: IssueStatusId, model: &mut Model) -> bool {
pub fn change_visible(model: &mut Model) {
let visible = ProjectPage::visible_issues(
crate::match_page!(model, Project),
model.epics(),
model.issue_statuses(),
model.epic_ids.len(),
model
.epic_ids
.iter()
.filter_map(|id| model.epics_by_id.get(id)),
model
.issue_status_ids
.iter()
.filter_map(|id| model.issue_statuses_by_id.get(id)),
model
.issue_ids
.iter()

View File

@ -197,10 +197,20 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
// issue statuses
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusesLoaded(v)) => {
model.issue_statuses = std::mem::take(v);
model
.issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position));
let len = v.len();
let mut issue_statuses = std::mem::take(v);
issue_statuses.sort_by(|a, b| a.position.cmp(&b.position));
model.issue_status_ids = Vec::with_capacity(len);
model.issue_statuses_by_id =
issue_statuses
.into_iter()
.fold(HashMap::with_capacity(len), |mut h, is| {
model.issue_status_ids.push(is.id);
h.insert(is.id, is);
h
});
orders.send_msg(Msg::ResourceChanged(
ResourceKind::IssueStatus,
OperationKind::ListLoaded,
@ -208,42 +218,33 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
));
}
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusCreated(is)) => {
let id = is.id;
model.issue_statuses.push(is.clone());
model
.issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position));
model.issue_status_ids.push(is.id);
model.issue_statuses_by_id.insert(is.id, is.clone());
orders.send_msg(Msg::ResourceChanged(
ResourceKind::IssueStatus,
OperationKind::SingleCreated,
Some(id),
Some(is.id),
));
}
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusUpdated(changed)) => {
let id = changed.id;
if let Some(idx) = model.issue_statuses.iter().position(|c| c.id == changed.id) {
std::mem::swap(&mut model.issue_statuses[idx], changed);
}
model
.issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position));
.issue_statuses_by_id
.insert(changed.id, changed.clone());
sort_issue_statuses(model);
orders.send_msg(Msg::ResourceChanged(
ResourceKind::IssueStatus,
OperationKind::SingleModified,
Some(id),
Some(changed.id),
));
}
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusDeleted(dropped_id, _count)) => {
let mut old = vec![];
std::mem::swap(&mut model.issue_statuses, &mut old);
for is in old {
if is.id != *dropped_id {
model.issue_statuses.push(is);
}
}
model
.issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position));
model.issue_statuses_by_id.remove(dropped_id);
model.issue_status_ids = std::mem::take(&mut model.issue_status_ids)
.into_iter()
.filter(|id| id != dropped_id)
.collect();
orders.send_msg(Msg::ResourceChanged(
ResourceKind::IssueStatus,
OperationKind::SingleRemoved,
@ -315,6 +316,11 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
issue.epic_id = o.epic_id;
}
}
orders.send_msg(Msg::ResourceChanged(
ResourceKind::Issue,
OperationKind::ListLoaded,
None,
));
}
// users
WsMsg::Project(WsMsgProject::ProjectUsersLoaded(v)) => {
@ -335,12 +341,12 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
Some(modal) => modal.id,
_ => return,
};
comments.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
if comments.iter().any(|c| c.issue_id != issue_id) {
return;
}
comments.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
model.comments = comments.clone();
for comment in std::mem::take(comments) {
model.comment_ids.push(comment.id);
model.comments_by_id.insert(comment.id, comment);
}
orders.send_msg(Msg::ResourceChanged(
@ -351,10 +357,7 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
}
WsMsg::Comment(WsMsgComment::CommentUpdated(comment)) => {
let comment_id = comment.id;
if let Some(idx) = model.comments.iter().position(|c| c.id == comment.id) {
let _ = std::mem::replace(&mut model.comments[idx], comment.clone());
model.comments_by_id.insert(comment.id, comment.clone());
}
orders.send_msg(Msg::ResourceChanged(
ResourceKind::Comment,
OperationKind::SingleModified,
@ -362,8 +365,8 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
));
}
WsMsg::Comment(WsMsgComment::CommentDeleted(comment_id, _count)) => {
if let Some(idx) = model.comments.iter().position(|c| c.id == *comment_id) {
model.comments.remove(idx);
if let Some(idx) = model.comment_ids.iter().position(|id| *id == *comment_id) {
model.comment_ids.remove(idx);
}
model.comments_by_id.remove(&comment_id);
orders.send_msg(Msg::ResourceChanged(
@ -407,10 +410,19 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
// epics
WsMsg::Epic(WsMsgEpic::EpicsLoaded(epics)) => {
model.epics = epics.clone();
for epic in epics {
model.epics_by_id.insert(epic.id, epic.clone());
}
let epics = std::mem::take(epics);
let len = epics.len();
model.epic_ids = Vec::with_capacity(len);
model.epics_by_id =
epics
.into_iter()
.fold(HashMap::with_capacity(len), |mut h, epic| {
model.epic_ids.push(epic.id);
h.insert(epic.id, epic);
h
});
orders.send_msg(Msg::ResourceChanged(
ResourceKind::Epic,
OperationKind::ListLoaded,
@ -419,9 +431,10 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
}
WsMsg::Epic(WsMsgEpic::EpicCreated(epic)) => {
let id = epic.id;
model.epics.push(epic.clone());
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
model.epic_ids.push(epic.id);
model.epic_ids.sort();
model.epics_by_id.insert(epic.id, epic.clone());
orders.send_msg(Msg::ResourceChanged(
ResourceKind::Epic,
OperationKind::SingleCreated,
@ -430,11 +443,8 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
}
WsMsg::Epic(WsMsgEpic::EpicUpdated(epic)) => {
let epic_id = epic.id;
if let Some(idx) = model.epics.iter().position(|e| e.id == epic.id) {
let _ = std::mem::replace(&mut model.epics[idx], epic.clone());
}
model.epics_by_id.insert(epic.id, epic.clone());
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
orders.send_msg(Msg::ResourceChanged(
ResourceKind::Epic,
OperationKind::SingleModified,
@ -442,11 +452,11 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
));
}
WsMsg::Epic(WsMsgEpic::EpicDeleted(id, _count)) => {
if let Some(idx) = model.epics.iter().position(|e| e.id == *id) {
model.epics.remove(idx);
}
model.epics_by_id.remove(id);
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
model.epic_ids = std::mem::take(&mut model.epic_ids)
.into_iter()
.filter(|epic_id| epic_id != id)
.collect();
orders.send_msg(Msg::ResourceChanged(
ResourceKind::Epic,
OperationKind::SingleRemoved,
@ -481,6 +491,17 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
};
}
pub fn sort_issue_statuses(model: &mut Model) {
let mut ids = model
.issue_status_ids
.iter()
.filter_map(|id| model.issue_statuses_by_id.get(id))
.map(|is| (is.id, is.position))
.collect::<Vec<_>>();
ids.sort_by(|(_, a), (_, b)| a.cmp(b));
model.issue_status_ids = ids.into_iter().map(|(id, _)| id).collect();
}
fn init_current_project(model: &mut Model, orders: &mut impl Orders<Msg>) {
if model.projects.is_empty() {
return;