Skip to main content

core_storage/
topology.rs

1use crate::pack::{push_u32, push_u32s, push_u64, read_u32, read_u32s, read_u64};
2use crate::types::Result as StoreResult;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::borrow::Cow;
5use std::cmp::Ordering;
6use std::collections::{BTreeSet, HashMap};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum Direction {
10    Out,
11    In,
12}
13
14/// Outcome of [`Topology::remove_edge`], distinguishing the two "not removed from overlay"
15/// cases that have different semantics for callers aware of a base CSR.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum RemoveEdgeOutcome {
18    /// Edge was present in the overlay and has been removed. `edge_count` was decremented.
19    RemovedFromOverlay,
20    /// Edge was NOT present in the overlay. A tombstone has been recorded in both
21    /// `out_tombstones` and `in_tombstones` so `TopologyView::neighbors` will subtract
22    /// it from the base CSR during reads and snapshot-merges. The edge may or may not
23    /// exist in the base CSR — the tombstone is a no-op if it does not.
24    TombstonedBase,
25}
26
27/// Flush the per-vertex insert buffer into the frozen block once it exceeds this.
28const INSERT_BUFFER: usize = 32;
29
30/// Sorted frozen neighbors plus an unsorted insert buffer. Serializes as `Vec<u32>`
31/// (merged, sorted, unique) so V6 snapshots stay HashMap-of-HashMap-of-Vec.
32#[derive(Debug, Default, Clone)]
33pub(crate) struct AdjList {
34    pub(crate) frozen: Vec<u32>,
35    pub(crate) delta: Vec<u32>,
36}
37
38impl AdjList {
39    fn contains(&self, id: u32) -> bool {
40        self.frozen.binary_search(&id).is_ok() || self.delta.contains(&id)
41    }
42
43    fn push(&mut self, id: u32) {
44        self.delta.push(id);
45        if self.delta.len() > INSERT_BUFFER {
46            self.flush();
47        }
48    }
49
50    fn flush(&mut self) {
51        if self.delta.is_empty() {
52            return;
53        }
54        if self.frozen.is_empty() {
55            self.delta.sort_unstable();
56            self.delta.dedup();
57            std::mem::swap(&mut self.frozen, &mut self.delta);
58            return;
59        }
60        self.frozen = merge_sorted_unique(&self.frozen, &self.delta);
61        self.delta.clear();
62    }
63
64    /// Return a sorted-unique merged view of frozen + delta.
65    ///
66    /// Borrows `frozen` directly when `delta` is empty (no allocation); allocates
67    /// only when delta is non-empty.
68    pub(crate) fn merged(&self) -> Cow<'_, [u32]> {
69        if self.delta.is_empty() {
70            Cow::Borrowed(&self.frozen)
71        } else {
72            Cow::Owned(merge_sorted_unique(&self.frozen, &self.delta))
73        }
74    }
75
76    fn remove(&mut self, id: u32) -> bool {
77        if let Ok(pos) = self.frozen.binary_search(&id) {
78            self.frozen.remove(pos);
79            return true;
80        }
81        if let Some(pos) = self.delta.iter().position(|&x| x == id) {
82            self.delta.swap_remove(pos);
83            return true;
84        }
85        false
86    }
87}
88
89impl Serialize for AdjList {
90    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
91        self.merged().serialize(serializer)
92    }
93}
94
95impl<'de> Deserialize<'de> for AdjList {
96    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
97        Ok(Self {
98            frozen: Vec::<u32>::deserialize(deserializer)?,
99            delta: Vec::new(),
100        })
101    }
102}
103
104fn merge_sorted_unique(frozen: &[u32], delta: &[u32]) -> Vec<u32> {
105    let mut extra: Vec<u32> = delta.to_vec();
106    extra.sort_unstable();
107    extra.dedup();
108    if frozen.is_empty() {
109        return extra;
110    }
111    if extra.is_empty() {
112        return frozen.to_vec();
113    }
114    let mut out = Vec::with_capacity(frozen.len() + extra.len());
115    let mut i = 0;
116    let mut j = 0;
117    while i < frozen.len() && j < extra.len() {
118        match frozen[i].cmp(&extra[j]) {
119            Ordering::Less => {
120                out.push(frozen[i]);
121                i += 1;
122            }
123            Ordering::Greater => {
124                out.push(extra[j]);
125                j += 1;
126            }
127            Ordering::Equal => {
128                out.push(frozen[i]);
129                i += 1;
130                j += 1;
131            }
132        }
133    }
134    out.extend_from_slice(&frozen[i..]);
135    out.extend_from_slice(&extra[j..]);
136    out
137}
138
139#[derive(Debug, Default, Clone, Serialize, Deserialize)]
140pub(crate) struct TypedAdjacency {
141    pub(crate) out: HashMap<u32, AdjList>,
142    pub(crate) inn: HashMap<u32, AdjList>,
143}
144
145/// Typed adjacency: per `(etype, dir, vertex)` a frozen sorted block plus an
146/// unsorted insert buffer. `neighbors` borrows the frozen block when the buffer
147/// is empty; otherwise it returns a sorted-unique merge.
148///
149/// On-disk (V6) shape is still `HashMap<u32, {out, inn: HashMap<u32, Vec<u32>>}>`.
150#[derive(Debug, Default, Clone, Serialize, Deserialize)]
151pub struct Topology {
152    pub(crate) by_type: HashMap<u32, TypedAdjacency>,
153    edge_count: u64,
154    /// Out-direction tombstones: edges deleted from the base CSR that are absent
155    /// from the overlay.  Keyed by `(etype → src → {deleted dst ids})`.
156    ///
157    /// Populated by `remove_edge` when the edge is not found in the overlay —
158    /// this records a deletion of a base-only edge so `TopologyView::neighbors`
159    /// can subtract it from the base CSR when merging.
160    ///
161    /// Not serialised: tombstones are eliminated at snapshot-merge time
162    /// (encode_v8 subtracts them when building the merged CSR section).
163    #[serde(skip)]
164    pub(crate) out_tombstones: HashMap<u32, HashMap<u32, BTreeSet<u32>>>,
165    /// In-direction tombstones: symmetric index of `out_tombstones`.
166    /// Keyed by `(etype → dst → {deleted src ids})` for O(log n) In lookups.
167    #[serde(skip)]
168    pub(crate) in_tombstones: HashMap<u32, HashMap<u32, BTreeSet<u32>>>,
169}
170
171impl Topology {
172    pub fn new() -> Self {
173        Self::default()
174    }
175
176    pub fn add_edge(&mut self, etype: u32, src: u32, dst: u32) -> bool {
177        let adj = self.by_type.entry(etype).or_default();
178        let dsts = adj.out.entry(src).or_default();
179        if dsts.contains(dst) {
180            return false;
181        }
182        dsts.push(dst);
183        let srcs = adj.inn.entry(dst).or_default();
184        assert!(
185            !srcs.contains(src),
186            "invariant: inn must not contain src as neighbor of dst when out lacks dst"
187        );
188        srcs.push(src);
189        self.edge_count += 1;
190        true
191    }
192
193    pub fn neighbors(&self, etype: u32, dir: Direction, v: u32) -> Cow<'_, [u32]> {
194        match self.adj_list(etype, dir, v) {
195            None => Cow::Borrowed(&[]),
196            Some(n) => n.merged(),
197        }
198    }
199
200    pub fn degree(&self, etype: u32, dir: Direction, v: u32) -> usize {
201        self.neighbors(etype, dir, v).as_ref().len()
202    }
203
204    pub fn edge_count(&self) -> u64 {
205        self.edge_count
206    }
207
208    /// Edge-type ids present in the graph, sorted ascending.
209    /// `by_type` stays a `HashMap` (smaller than a BTreeMap migrate; snapshot encoding unchanged);
210    /// this method collect+sorts keys so iteration is deterministic.
211    pub fn etypes(&self) -> impl Iterator<Item = u32> + '_ {
212        let mut ids: Vec<u32> = self.by_type.keys().copied().collect();
213        ids.sort_unstable();
214        ids.into_iter()
215    }
216
217    /// Iterate all directed out-edges as `(etype, src, dst)` triples.
218    ///
219    /// Yields every edge in the overlay topology in arbitrary order.  Used by
220    /// callers (e.g. `core-api`) that need to walk the full edge set without
221    /// direct access to the private `by_type` field.
222    pub fn all_edges(&self) -> impl Iterator<Item = (u32, u32, u32)> + '_ {
223        self.by_type.iter().flat_map(|(&etype, adj)| {
224            adj.out.iter().flat_map(move |(&src, al)| {
225                al.merged()
226                    .into_owned()
227                    .into_iter()
228                    .map(move |dst| (etype, src, dst))
229            })
230        })
231    }
232
233    /// Remove an edge and return the outcome.
234    ///
235    /// - [`RemoveEdgeOutcome::RemovedFromOverlay`]: edge was in the overlay and has been
236    ///   removed; `edge_count` was decremented.
237    /// - [`RemoveEdgeOutcome::TombstonedBase`]: edge was NOT in the overlay; a tombstone
238    ///   was recorded in `out_tombstones` / `in_tombstones` so `TopologyView::neighbors`
239    ///   will subtract it from the base CSR.  The tombstone is harmless if the edge does
240    ///   not exist in the base CSR either.
241    pub fn remove_edge(&mut self, etype: u32, src: u32, dst: u32) -> RemoveEdgeOutcome {
242        let found_in_overlay = (|| {
243            let adj = self.by_type.get_mut(&etype)?;
244            let dsts = adj.out.get_mut(&src)?;
245            if !dsts.remove(dst) {
246                return None;
247            }
248            let srcs = adj
249                .inn
250                .get_mut(&dst)
251                .expect("invariant: inn bucket must exist when out contains dst");
252            assert!(
253                srcs.remove(src),
254                "invariant: inn must contain src when out contained dst"
255            );
256            self.edge_count -= 1;
257            Some(())
258        })()
259        .is_some();
260
261        if !found_in_overlay {
262            // The edge was not in the overlay (may be present only in the mmap'd base
263            // CSR).  Record a tombstone so `TopologyView::neighbors` can subtract it
264            // from the base when merging overlay + base for reads and for snapshot-merge.
265            self.out_tombstones
266                .entry(etype)
267                .or_default()
268                .entry(src)
269                .or_default()
270                .insert(dst);
271            self.in_tombstones
272                .entry(etype)
273                .or_default()
274                .entry(dst)
275                .or_default()
276                .insert(src);
277            RemoveEdgeOutcome::TombstonedBase
278        } else {
279            RemoveEdgeOutcome::RemovedFromOverlay
280        }
281    }
282
283    /// Return the set of `dst` ids tombstoned for `(etype, Direction::Out, src)`.
284    ///
285    /// Used by `TopologyView::neighbors` to subtract deleted edges from the base CSR.
286    pub fn out_tombstones_for(&self, etype: u32, src: u32) -> Option<&BTreeSet<u32>> {
287        self.out_tombstones.get(&etype)?.get(&src)
288    }
289
290    /// Return the set of `src` ids tombstoned for `(etype, Direction::In, dst)`.
291    pub fn in_tombstones_for(&self, etype: u32, dst: u32) -> Option<&BTreeSet<u32>> {
292        self.in_tombstones.get(&etype)?.get(&dst)
293    }
294
295    fn adj_list(&self, etype: u32, dir: Direction, v: u32) -> Option<&AdjList> {
296        self.by_type.get(&etype).and_then(|adj| match dir {
297            Direction::Out => adj.out.get(&v),
298            Direction::In => adj.inn.get(&v),
299        })
300    }
301
302    /// V7 packed CSR: etype count, then per etype (id, out map, in map), then edge_count.
303    /// Each adjacency map is vertex-count + (vertex, length-prefixed frozen neighbor array).
304    /// Deltas are merged into frozen on pack; unpack leaves deltas empty.
305    pub(crate) fn pack(&self, out: &mut Vec<u8>) {
306        let mut etypes: Vec<u32> = self.by_type.keys().copied().collect();
307        etypes.sort_unstable();
308        push_u32(out, etypes.len() as u32);
309        for et in etypes {
310            push_u32(out, et);
311            let adj = &self.by_type[&et];
312            pack_adj_map(out, &adj.out);
313            pack_adj_map(out, &adj.inn);
314        }
315        push_u64(out, self.edge_count);
316    }
317
318    pub(crate) fn unpack(src: &[u8]) -> StoreResult<(Self, usize)> {
319        let mut pos = 0usize;
320        let n_etypes = read_u32(src, &mut pos)? as usize;
321        let mut by_type = HashMap::with_capacity(n_etypes);
322        for _ in 0..n_etypes {
323            let et = read_u32(src, &mut pos)?;
324            let out = unpack_adj_map(src, &mut pos)?;
325            let inn = unpack_adj_map(src, &mut pos)?;
326            by_type.insert(et, TypedAdjacency { out, inn });
327        }
328        let edge_count = read_u64(src, &mut pos)?;
329        Ok((
330            Self {
331                by_type,
332                edge_count,
333                out_tombstones: HashMap::new(),
334                in_tombstones: HashMap::new(),
335            },
336            pos,
337        ))
338    }
339}
340
341fn pack_adj_map(out: &mut Vec<u8>, map: &HashMap<u32, AdjList>) {
342    let mut verts: Vec<u32> = map.keys().copied().collect();
343    verts.sort_unstable();
344    push_u32(out, verts.len() as u32);
345    for v in verts {
346        push_u32(out, v);
347        let list = &map[&v];
348        push_u32s(out, list.merged().as_ref());
349    }
350}
351
352fn unpack_adj_map(src: &[u8], pos: &mut usize) -> StoreResult<HashMap<u32, AdjList>> {
353    let n = read_u32(src, pos)? as usize;
354    let mut map = HashMap::with_capacity(n);
355    for _ in 0..n {
356        let v = read_u32(src, pos)?;
357        let frozen = read_u32s(src, pos)?;
358        map.insert(
359            v,
360            AdjList {
361                frozen,
362                delta: Vec::new(),
363            },
364        );
365    }
366    Ok(map)
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn edges_are_typed_directed_sorted_deduped() {
375        let mut t = Topology::new();
376        assert!(t.add_edge(0, 5, 9));
377        assert!(t.add_edge(0, 5, 3));
378        assert!(!t.add_edge(0, 5, 9)); // duplicate
379        assert!(t.add_edge(1, 5, 9)); // same pair, different type: distinct edge
380        assert_eq!(t.neighbors(0, Direction::Out, 5).as_ref(), &[3, 9]); // sorted
381        assert_eq!(t.neighbors(0, Direction::In, 9).as_ref(), &[5]);
382        assert_eq!(t.neighbors(0, Direction::Out, 999).as_ref(), &[] as &[u32]);
383        assert_eq!(t.degree(0, Direction::Out, 5), 2);
384        assert_eq!(t.edge_count(), 3);
385    }
386
387    #[test]
388    fn remove_edge_updates_both_sides_and_count() {
389        let mut t = Topology::new();
390        t.add_edge(0, 1, 2);
391        t.add_edge(0, 1, 3);
392        assert_eq!(
393            t.remove_edge(0, 1, 2),
394            RemoveEdgeOutcome::RemovedFromOverlay
395        );
396        assert_eq!(t.remove_edge(0, 1, 2), RemoveEdgeOutcome::TombstonedBase); // idempotent → tombstone
397        assert_eq!(t.remove_edge(9, 1, 2), RemoveEdgeOutcome::TombstonedBase); // unknown type → tombstone
398        assert_eq!(t.neighbors(0, Direction::Out, 1).as_ref(), &[3]);
399        assert_eq!(t.neighbors(0, Direction::In, 2).as_ref(), &[] as &[u32]);
400        assert_eq!(t.edge_count(), 1);
401        // re-add after remove works
402        assert!(t.add_edge(0, 1, 2));
403        assert_eq!(t.edge_count(), 2);
404    }
405
406    #[test]
407    fn etypes_empty_multiple_and_sorted() {
408        let empty = Topology::new();
409        assert_eq!(empty.etypes().collect::<Vec<_>>(), Vec::<u32>::new());
410
411        let mut t = Topology::new();
412        t.add_edge(3, 0, 1);
413        t.add_edge(1, 0, 1);
414        t.add_edge(3, 1, 2); // existing type, must not duplicate
415        t.add_edge(2, 0, 2);
416        assert_eq!(t.etypes().collect::<Vec<_>>(), vec![1, 2, 3]);
417    }
418
419    #[test]
420    fn insert_buffer_defers_sort_until_threshold_then_neighbors_sorted_unique() {
421        let mut t = Topology::new();
422        // Reverse inserts: a full sort on every add would keep the scan path borrowed.
423        for dst in (0u32..32).rev() {
424            assert!(t.add_edge(0, 0, dst));
425            let nbrs = t.neighbors(0, Direction::Out, 0);
426            assert!(
427                matches!(nbrs, Cow::Owned(_)),
428                "delta still dirty at {} edges; neighbors must take the owned merge path",
429                32 - dst
430            );
431            assert!(
432                nbrs.windows(2).all(|w| w[0] < w[1]),
433                "dirty merge must be sorted unique, got {nbrs:?}"
434            );
435            assert_eq!(nbrs.len(), (32 - dst) as usize);
436            let n = t.adj_list(0, Direction::Out, 0).unwrap();
437            assert!(
438                n.frozen.is_empty(),
439                "must not flush frozen before threshold"
440            );
441            assert_eq!(n.delta.len(), (32 - dst) as usize);
442        }
443        assert_eq!(t.degree(0, Direction::Out, 0), 32);
444        assert_eq!(t.neighbors(0, Direction::In, 31).as_ref(), &[0]);
445
446        assert!(t.add_edge(0, 0, 32)); // buffer len > 32 → merge into frozen
447        let nbrs = t.neighbors(0, Direction::Out, 0);
448        assert!(
449            matches!(nbrs, Cow::Borrowed(_)),
450            "after threshold flush, scan path must borrow the frozen block"
451        );
452        let expected: Vec<u32> = (0..33).collect();
453        assert_eq!(nbrs.as_ref(), expected.as_slice());
454        let n = t.adj_list(0, Direction::Out, 0).unwrap();
455        assert!(n.delta.is_empty());
456        assert_eq!(n.frozen, expected);
457        assert_eq!(t.edge_count(), 33);
458        assert_eq!(t.neighbors(0, Direction::In, 32).as_ref(), &[0]);
459        assert!(!t.add_edge(0, 0, 7));
460    }
461
462    #[test]
463    fn remove_edge_from_delta_and_from_frozen() {
464        let mut t = Topology::new();
465        for dst in 0u32..10 {
466            assert!(t.add_edge(0, 1, dst));
467        }
468        assert!(matches!(t.neighbors(0, Direction::Out, 1), Cow::Owned(_)));
469        assert_eq!(
470            t.remove_edge(0, 1, 7),
471            RemoveEdgeOutcome::RemovedFromOverlay
472        );
473        assert_eq!(t.remove_edge(0, 1, 7), RemoveEdgeOutcome::TombstonedBase);
474        assert_eq!(t.neighbors(0, Direction::In, 7).as_ref(), &[] as &[u32]);
475        assert_eq!(
476            t.neighbors(0, Direction::Out, 1).as_ref(),
477            &[0, 1, 2, 3, 4, 5, 6, 8, 9]
478        );
479        assert_eq!(t.edge_count(), 9);
480        assert!(t.add_edge(0, 1, 7));
481        assert_eq!(t.edge_count(), 10);
482
483        let mut t = Topology::new();
484        for dst in 0u32..33 {
485            assert!(t.add_edge(0, 1, dst));
486        }
487        assert!(matches!(
488            t.neighbors(0, Direction::Out, 1),
489            Cow::Borrowed(_)
490        ));
491        assert_eq!(
492            t.remove_edge(0, 1, 0),
493            RemoveEdgeOutcome::RemovedFromOverlay
494        );
495        assert_eq!(
496            t.remove_edge(0, 1, 32),
497            RemoveEdgeOutcome::RemovedFromOverlay
498        );
499        assert_eq!(t.edge_count(), 31);
500        assert_eq!(t.neighbors(0, Direction::In, 0).as_ref(), &[] as &[u32]);
501        assert_eq!(t.neighbors(0, Direction::In, 16).as_ref(), &[1]);
502        let expected: Vec<u32> = (1..32).collect();
503        assert_eq!(
504            t.neighbors(0, Direction::Out, 1).as_ref(),
505            expected.as_slice()
506        );
507        assert!(t.add_edge(0, 1, 0));
508        assert_eq!(t.edge_count(), 32);
509    }
510
511    #[test]
512    fn serde_wire_is_hashmap_of_hashmap_of_vec() {
513        #[derive(Serialize, Deserialize, PartialEq, Debug)]
514        struct WireAdj {
515            out: HashMap<u32, Vec<u32>>,
516            inn: HashMap<u32, Vec<u32>>,
517        }
518        #[derive(Serialize, Deserialize, PartialEq, Debug)]
519        struct Wire {
520            by_type: HashMap<u32, WireAdj>,
521            edge_count: u64,
522        }
523
524        let mut by_type = HashMap::new();
525        by_type.insert(
526            0,
527            WireAdj {
528                out: HashMap::from([(5, vec![3, 9])]),
529                inn: HashMap::from([(3, vec![5]), (9, vec![5])]),
530            },
531        );
532        let wire = Wire {
533            by_type,
534            edge_count: 2,
535        };
536        let encoded = bincode::serialize(&wire).unwrap();
537        let t: Topology = bincode::deserialize(&encoded).unwrap();
538        assert_eq!(t.neighbors(0, Direction::Out, 5).as_ref(), &[3, 9]);
539        assert_eq!(t.neighbors(0, Direction::In, 3).as_ref(), &[5]);
540        assert_eq!(t.neighbors(0, Direction::In, 9).as_ref(), &[5]);
541        assert_eq!(t.edge_count(), 2);
542        assert!(matches!(
543            t.neighbors(0, Direction::Out, 5),
544            Cow::Borrowed(_)
545        ));
546
547        let roundtrip: Wire = bincode::deserialize(&bincode::serialize(&t).unwrap()).unwrap();
548        assert_eq!(roundtrip.edge_count, 2);
549        assert_eq!(roundtrip.by_type[&0].out[&5], vec![3, 9]);
550        assert_eq!(roundtrip.by_type[&0].inn[&3], vec![5]);
551        assert_eq!(roundtrip.by_type[&0].inn[&9], vec![5]);
552
553        // Dirty delta still encodes as a sorted unique Vec.
554        let mut dirty = Topology::new();
555        for dst in (0u32..10).rev() {
556            dirty.add_edge(1, 0, dst);
557        }
558        assert!(matches!(
559            dirty.neighbors(1, Direction::Out, 0),
560            Cow::Owned(_)
561        ));
562        let dirty_wire: Wire = bincode::deserialize(&bincode::serialize(&dirty).unwrap()).unwrap();
563        assert_eq!(dirty_wire.edge_count, 10);
564        assert_eq!(
565            dirty_wire.by_type[&1].out[&0],
566            (0..10).collect::<Vec<u32>>()
567        );
568        for dst in 0..10 {
569            assert_eq!(dirty_wire.by_type[&1].inn[&dst], vec![0]);
570        }
571    }
572
573    #[test]
574    fn pack_roundtrip_merges_delta_and_restores_frozen() {
575        let mut t = Topology::new();
576        for dst in (0u32..10).rev() {
577            t.add_edge(2, 1, dst);
578        }
579        t.add_edge(0, 5, 9);
580        assert!(matches!(t.neighbors(2, Direction::Out, 1), Cow::Owned(_)));
581        let mut buf = Vec::new();
582        t.pack(&mut buf);
583        let (back, consumed) = Topology::unpack(&buf).unwrap();
584        assert_eq!(consumed, buf.len());
585        assert_eq!(back.edge_count(), 11);
586        let expected: Vec<u32> = (0..10).collect();
587        assert_eq!(
588            back.neighbors(2, Direction::Out, 1).as_ref(),
589            expected.as_slice()
590        );
591        assert!(matches!(
592            back.neighbors(2, Direction::Out, 1),
593            Cow::Borrowed(_)
594        ));
595        assert_eq!(back.neighbors(0, Direction::Out, 5).as_ref(), &[9]);
596        assert_eq!(back.neighbors(0, Direction::In, 9).as_ref(), &[5]);
597        assert_eq!(back.etypes().collect::<Vec<_>>(), vec![0, 2]);
598    }
599}