nested state

This commit is contained in:
Evan Almloff 2022-04-19 20:12:57 -05:00
parent f4689a4e27
commit 7c30d93a3d
5 changed files with 270 additions and 30 deletions

View file

@ -304,7 +304,7 @@ flowchart TB
end end
``` ```
To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState. To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct.
```rust ```rust
use dioxus_native_core::node_ref::*; use dioxus_native_core::node_ref::*;
@ -436,6 +436,48 @@ struct ToyState {
} }
``` ```
Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and chaning properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.
```rust
fn main(){
fn app(cx: Scope) -> Element {
cx.render(rsx!{
div{
color: "red",
"hello world"
}
})
}
let vdom = VirtualDom::new(app);
let rdom: RealDom<ToyState> = RealDom::new();
let mutations = dom.rebuild();
// update the structure of the real_dom tree
let to_update = rdom.apply_mutations(vec![mutations]);
let mut ctx = AnyMap::new();
// set the font size to 3.3
ctx.insert(3.3);
// update the ToyState for nodes in the real_dom tree
let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();
// we need to run the vdom in a async runtime
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
loop{
let wait = vdom.wait_for_work();
let mutations = vdom.work_with_deadline(|| false);
let to_update = rdom.apply_mutations(mutations);
let mut ctx = AnyMap::new();
ctx.insert(3.3);
let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();
// render...
}
})
}
```
## Layout ## Layout
For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style. For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style.

View file

@ -4,6 +4,7 @@ mod sorted_slice;
use dioxus_native_core::state::MemberId; use dioxus_native_core::state::MemberId;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::format_ident;
use quote::{quote, ToTokens, __private::Span}; use quote::{quote, ToTokens, __private::Span};
use sorted_slice::StrSlice; use sorted_slice::StrSlice;
use syn::{ use syn::{
@ -26,7 +27,10 @@ enum DepKind {
Parent, Parent,
} }
#[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))] #[proc_macro_derive(
State,
attributes(node_dep_state, child_dep_state, parent_dep_state, state)
)]
pub fn state_macro_derive(input: TokenStream) -> TokenStream { pub fn state_macro_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap(); let ast = syn::parse(input).unwrap();
impl_derive_macro(&ast) impl_derive_macro(&ast)
@ -89,8 +93,51 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let type_name_str = type_name.to_string(); let type_name_str = type_name.to_string();
let child_states = &state_strct.child_states;
let member_size = state_strct.state_members.len();
let child_state_ty = child_states.iter().map(|m| &m.ty);
let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect();
let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| {
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
if i == 0 {
quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1;
const #ident: usize = #member_size + #ty::SIZE;)
} else {
let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1;
const #ident: usize = #prev_ident + #ty::SIZE;)
}
});
let sum_idents: Vec<_> = std::iter::once(quote!(#member_size))
.chain((0..child_states.len()).map(|i| {
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
quote!(#ident)
}))
.collect();
let child_state_ranges: Vec<_> = (0..child_state_ty.len())
.map(|i| {
let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
let previous = if i == 0 {
quote!(#member_size)
} else {
let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
quote!(#ident)
};
quote!(#previous..=#current)
})
.collect();
let gen = quote! { let gen = quote! {
#(
#sum_const_declarations
)*
impl State for #type_name{ impl State for #type_name{
const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*;
fn update_node_dep_state<'a>( fn update_node_dep_state<'a>(
&'a mut self, &'a mut self,
ty: dioxus_native_core::state::MemberId, ty: dioxus_native_core::state::MemberId,
@ -99,10 +146,26 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
ctx: &anymap::AnyMap, ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::NodeStatesChanged>{ ) -> Option<dioxus_native_core::state::NodeStatesChanged>{
use dioxus_native_core::state::NodeDepState as _; use dioxus_native_core::state::NodeDepState as _;
use dioxus_native_core::state::State as _;
match ty.0{ match ty.0{
#( #(
#node_ids => #node_dep_state_fields, #node_ids => #node_dep_state_fields,
)* )*
#(
#child_state_ranges => {
self.#child_state_idents.update_node_dep_state(
ty - #sum_idents,
node,
vdom,
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
changed
})
}
)*
_ => panic!("{:?} not in {}", ty, #type_name_str), _ => panic!("{:?} not in {}", ty, #type_name_str),
} }
} }
@ -120,6 +183,25 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
#( #(
#parent_ids => #parent_dep_state_fields, #parent_ids => #parent_dep_state_fields,
)* )*
#(
#child_state_ranges => {
self.#child_state_idents.update_parent_dep_state(
ty - #sum_idents,
node,
vdom,
parent.map(|p| &p.#child_state_idents),
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
for id in &mut changed.parent_dep{
*id += #sum_idents;
}
changed
})
}
)*
_ => panic!("{:?} not in {}", ty, #type_name_str), _ => panic!("{:?} not in {}", ty, #type_name_str),
} }
} }
@ -129,13 +211,32 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
ty: dioxus_native_core::state::MemberId, ty: dioxus_native_core::state::MemberId,
node: &'a dioxus_core::VNode<'a>, node: &'a dioxus_core::VNode<'a>,
vdom: &'a dioxus_core::VirtualDom, vdom: &'a dioxus_core::VirtualDom,
children: &[&Self], children: &Vec<&Self>,
ctx: &anymap::AnyMap, ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::ChildStatesChanged>{ ) -> Option<dioxus_native_core::state::ChildStatesChanged>{
use dioxus_native_core::state::ChildDepState as _; use dioxus_native_core::state::ChildDepState as _;
match ty.0{ match ty.0{
#( #(
#child_ids => {#child_dep_state_fields}, #child_ids => #child_dep_state_fields,
)*
#(
#child_state_ranges => {
self.#child_state_idents.update_child_dep_state(
ty - #sum_idents,
node,
vdom,
&children.iter().map(|p| &p.#child_state_idents).collect(),
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
for id in &mut changed.child_dep{
*id += #sum_idents;
}
changed
})
}
)* )*
_ => panic!("{:?} not in {}", ty, #type_name_str), _ => panic!("{:?} not in {}", ty, #type_name_str),
} }
@ -146,6 +247,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
#(if #child_types::NODE_MASK.overlaps(mask) { #(if #child_types::NODE_MASK.overlaps(mask) {
dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone)); dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone));
})* })*
#(
dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents));
)*
dep_types dep_types
} }
@ -154,6 +258,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
#(if #parent_types::NODE_MASK.overlaps(mask) { #(if #parent_types::NODE_MASK.overlaps(mask) {
dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone)); dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone));
})* })*
#(
dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents));
)*
dep_types dep_types
} }
@ -162,6 +269,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
#(if #node_types::NODE_MASK.overlaps(mask) { #(if #node_types::NODE_MASK.overlaps(mask) {
dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone)); dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone));
})* })*
#(
dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents));
)*
dep_types dep_types
} }
} }
@ -186,6 +296,7 @@ impl Struct {
struct StateStruct<'a> { struct StateStruct<'a> {
state_members: Vec<StateMember<'a>>, state_members: Vec<StateMember<'a>>,
child_states: Vec<&'a Member>,
} }
impl<'a> StateStruct<'a> { impl<'a> StateStruct<'a> {
@ -203,6 +314,20 @@ impl<'a> StateStruct<'a> {
} }
}); });
let child_states = strct
.members
.iter()
.zip(fields.iter())
.filter(|(_, f)| {
f.attrs.iter().any(|a| {
a.path
.get_ident()
.filter(|i| i.to_string().as_str() == "state")
.is_some()
})
})
.map(|(m, _)| m);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct DepNode<'a> { struct DepNode<'a> {
state_mem: StateMember<'a>, state_mem: StateMember<'a>,
@ -350,7 +475,10 @@ impl<'a> StateStruct<'a> {
.flat_map(|r| r.flatten().into_iter()) .flat_map(|r| r.flatten().into_iter())
.collect(); .collect();
Ok(Self { state_members }) Ok(Self {
state_members,
child_states: child_states.collect(),
})
} }
} }
} }
@ -484,23 +612,23 @@ impl<'a> StateMember<'a> {
DepKind::Node => { DepKind::Node => {
quote! { quote! {
dioxus_native_core::state::NodeStatesChanged{ dioxus_native_core::state::NodeStatesChanged{
node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
} }
} }
} }
DepKind::Child => { DepKind::Child => {
quote! { quote! {
dioxus_native_core::state::ChildStatesChanged{ dioxus_native_core::state::ChildStatesChanged{
node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
child_dep: &[#(dioxus_native_core::state::MemberId(#child_dep), )*], child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*],
} }
} }
} }
DepKind::Parent => { DepKind::Parent => {
quote! { quote! {
dioxus_native_core::state::ParentStatesChanged{ dioxus_native_core::state::ParentStatesChanged{
node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
parent_dep: &[#(dioxus_native_core::state::MemberId(#parent_dep), )*], parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*],
} }
} }
} }
@ -515,7 +643,6 @@ impl<'a> StateMember<'a> {
match self.dep_kind { match self.dep_kind {
DepKind::Node => { DepKind::Node => {
quote!({ quote!({
// println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){
Some(#states_changed) Some(#states_changed)
} else{ } else{
@ -525,7 +652,6 @@ impl<'a> StateMember<'a> {
} }
DepKind::Child => { DepKind::Child => {
quote!({ quote!({
// println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){ if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){
Some(#states_changed) Some(#states_changed)
} else{ } else{
@ -535,7 +661,6 @@ impl<'a> StateMember<'a> {
} }
DepKind::Parent => { DepKind::Parent => {
quote!({ quote!({
// println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){ if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){
Some(#states_changed) Some(#states_changed)
} else{ } else{
@ -548,7 +673,6 @@ impl<'a> StateMember<'a> {
match self.dep_kind { match self.dep_kind {
DepKind::Node => { DepKind::Node => {
quote!({ quote!({
// println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
if self.#ident.reduce(#node_view, &(), #get_ctx){ if self.#ident.reduce(#node_view, &(), #get_ctx){
Some(#states_changed) Some(#states_changed)
} else{ } else{
@ -558,7 +682,6 @@ impl<'a> StateMember<'a> {
} }
DepKind::Child => { DepKind::Child => {
quote!({ quote!({
// println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){ if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){
Some(#states_changed) Some(#states_changed)
} else{ } else{
@ -568,7 +691,6 @@ impl<'a> StateMember<'a> {
} }
DepKind::Parent => { DepKind::Parent => {
quote!({ quote!({
println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
if self.#ident.reduce(#node_view, Some(&()), #get_ctx){ if self.#ident.reduce(#node_view, Some(&()), #get_ctx){
Some(#states_changed) Some(#states_changed)
} else{ } else{

View file

@ -8,12 +8,36 @@ use dioxus_native_core::real_dom::*;
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
use dioxus_native_core_macro::State; use dioxus_native_core_macro::State;
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart1 {
#[child_dep_state(child_counter)]
child_counter: ChildDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart2 {
#[parent_dep_state(parent_counter)]
parent_counter: ParentDepCallCounter,
}
#[derive(Debug, Clone, Default, State)]
struct CallCounterStatePart3 {
#[node_dep_state()]
node_counter: NodeDepCallCounter,
}
#[derive(Debug, Clone, Default, State)] #[derive(Debug, Clone, Default, State)]
struct CallCounterState { struct CallCounterState {
#[child_dep_state(child_counter)] #[child_dep_state(child_counter)]
child_counter: ChildDepCallCounter, child_counter: ChildDepCallCounter,
#[state]
part2: CallCounterStatePart2,
#[parent_dep_state(parent_counter)] #[parent_dep_state(parent_counter)]
parent_counter: ParentDepCallCounter, parent_counter: ParentDepCallCounter,
#[state]
part1: CallCounterStatePart1,
#[state]
part3: CallCounterStatePart3,
#[node_dep_state()] #[node_dep_state()]
node_counter: NodeDepCallCounter, node_counter: NodeDepCallCounter,
} }
@ -261,8 +285,11 @@ fn state_reduce_initally_called_minimally() {
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
dom.traverse_depth_first(|n| { dom.traverse_depth_first(|n| {
assert_eq!(n.state.part1.child_counter.0, 1);
assert_eq!(n.state.child_counter.0, 1); assert_eq!(n.state.child_counter.0, 1);
assert_eq!(n.state.part2.parent_counter.0, 1);
assert_eq!(n.state.parent_counter.0, 1); assert_eq!(n.state.parent_counter.0, 1);
assert_eq!(n.state.part3.node_counter.0, 1);
assert_eq!(n.state.node_counter.0, 1); assert_eq!(n.state.node_counter.0, 1);
}); });
} }
@ -329,6 +356,7 @@ fn state_reduce_parent_called_minimally_on_update() {
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
dom.traverse_depth_first(|n| { dom.traverse_depth_first(|n| {
assert_eq!(n.state.part2.parent_counter.0, 2);
assert_eq!(n.state.parent_counter.0, 2); assert_eq!(n.state.parent_counter.0, 2);
}); });
} }
@ -398,6 +426,10 @@ fn state_reduce_child_called_minimally_on_update() {
dom.traverse_depth_first(|n| { dom.traverse_depth_first(|n| {
println!("{:?}", n); println!("{:?}", n);
assert_eq!(
n.state.part1.child_counter.0,
if n.id.0 > 4 { 1 } else { 2 }
);
assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
}); });
} }
@ -487,3 +519,15 @@ fn dependancies_order_independant() {
assert_eq!(&n.state.c, &c); assert_eq!(&n.state.c, &c);
}); });
} }
#[derive(Clone, Default, State)]
struct DependanciesStateTest {
#[node_dep_state(c)]
b: BDepCallCounter,
#[node_dep_state()]
c: CDepCallCounter,
#[node_dep_state(b)]
a: ADepCallCounter,
#[state]
child: UnorderedDependanciesState,
}

View file

@ -308,7 +308,7 @@ impl<S: State> RealDom<S> {
node.state.update_node_dep_state(id, vnode, vdom, &ctx) node.state.update_node_dep_state(id, vnode, vdom, &ctx)
{ {
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in members_effected.node_dep { for m in &members_effected.node_dep {
if let Err(idx) = ids.binary_search(m) { if let Err(idx) = ids.binary_search(m) {
ids.insert(idx, *m); ids.insert(idx, *m);
} }
@ -348,12 +348,12 @@ impl<S: State> RealDom<S> {
{ {
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in members_effected.node_dep { for m in members_effected.node_dep {
if let Err(idx) = ids.binary_search(m) { if let Err(idx) = ids.binary_search(&m) {
ids.insert(idx, *m); ids.insert(idx, m);
} }
} }
for m in members_effected.child_dep { for m in members_effected.child_dep {
changed.push(*m); changed.push(m);
} }
} }
i += 1; i += 1;
@ -416,12 +416,12 @@ impl<S: State> RealDom<S> {
{ {
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in members_effected.node_dep { for m in members_effected.node_dep {
if let Err(idx) = ids.binary_search(m) { if let Err(idx) = ids.binary_search(&m) {
ids.insert(idx, *m); ids.insert(idx, m);
} }
} }
for m in members_effected.parent_dep { for m in members_effected.parent_dep {
changed.push(*m); changed.push(m);
} }
} }
i += 1; i += 1;

View file

@ -1,4 +1,8 @@
use std::{cmp::Ordering, fmt::Debug}; use std::{
cmp::Ordering,
fmt::Debug,
ops::{Add, AddAssign, Sub, SubAssign},
};
use anymap::AnyMap; use anymap::AnyMap;
use dioxus_core::VNode; use dioxus_core::VNode;
@ -81,22 +85,24 @@ pub trait NodeDepState {
#[derive(Debug)] #[derive(Debug)]
pub struct ChildStatesChanged { pub struct ChildStatesChanged {
pub node_dep: &'static [MemberId], pub node_dep: Vec<MemberId>,
pub child_dep: &'static [MemberId], pub child_dep: Vec<MemberId>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ParentStatesChanged { pub struct ParentStatesChanged {
pub node_dep: &'static [MemberId], pub node_dep: Vec<MemberId>,
pub parent_dep: &'static [MemberId], pub parent_dep: Vec<MemberId>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct NodeStatesChanged { pub struct NodeStatesChanged {
pub node_dep: &'static [MemberId], pub node_dep: Vec<MemberId>,
} }
pub trait State: Default + Clone { pub trait State: Default + Clone {
const SIZE: usize;
fn update_node_dep_state<'a>( fn update_node_dep_state<'a>(
&'a mut self, &'a mut self,
ty: MemberId, ty: MemberId,
@ -123,7 +129,7 @@ pub trait State: Default + Clone {
ty: MemberId, ty: MemberId,
node: &'a VNode<'a>, node: &'a VNode<'a>,
vdom: &'a dioxus_core::VirtualDom, vdom: &'a dioxus_core::VirtualDom,
children: &[&Self], children: &Vec<&Self>,
ctx: &AnyMap, ctx: &AnyMap,
) -> Option<ChildStatesChanged>; ) -> Option<ChildStatesChanged>;
/// This must be a valid resolution order. (no nodes updated before a state they rely on) /// This must be a valid resolution order. (no nodes updated before a state they rely on)
@ -165,3 +171,29 @@ impl NodeDepState for () {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MemberId(pub usize); pub struct MemberId(pub usize);
impl Sub<usize> for MemberId {
type Output = MemberId;
fn sub(self, rhs: usize) -> Self::Output {
MemberId(self.0 - rhs)
}
}
impl Add<usize> for MemberId {
type Output = MemberId;
fn add(self, rhs: usize) -> Self::Output {
MemberId(self.0 + rhs)
}
}
impl SubAssign<usize> for MemberId {
fn sub_assign(&mut self, rhs: usize) {
*self = *self - rhs;
}
}
impl AddAssign<usize> for MemberId {
fn add_assign(&mut self, rhs: usize) {
*self = *self + rhs;
}
}