1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! An order-independent digest of a set of keys, for catching a mirror that has
//! silently stopped matching the set it is supposed to hold.
//!
//! Shared by both sides deliberately. The server folds what it believes a client
//! should hold, the client folds what it actually holds, and the two compare. If
//! each computed its own fold, a disagreement in the arithmetic would be
//! indistinguishable from a disagreement about the world, and the recovery
//! machinery would fire forever chasing a bug that was only ever in the hashing.
//! So there is one implementation, and it lives in the lower crate:
//! `plaza_server_utils` re-exports it beside the relevance machinery.
/// An order-independent digest of a set of keys, maintainable incrementally.
///
/// A delta-relevance stream has a silent failure mode: the client applies
/// `entered`/`left` to keep a local mirror, and if one delta is lost, malformed,
/// or misapplied, the mirror is wrong **for good**, with no symptom. Bandwidth
/// looks normal, positions look normal, and the only evidence is on the screen.
/// The cure is for both sides to summarise their set cheaply and compare.
///
/// Order independence is the requirement that shapes this: two peers holding the
/// same set may iterate it in different orders, so the digest must not depend on
/// order. Summation gives that, and unlike XOR it does not silently cancel
/// duplicates. Because the combine is addition, a key can be added or removed in
/// O(1), so a client maintains the digest as entities enter and leave rather than
/// rehashing everything each tick.
///
/// The key is a `u64` you choose, which is the important flexibility: hash a bare
/// index to check *membership*, or pack an index with a generation to check that
/// both sides agree on the *occupant* too.
///
/// ```ignore
/// // Server, once per send: summarise what this client should now hold.
/// let digest = SetDigest::from_keys(visible.iter().map(u64::from)).digest();
///
/// // Client, right after applying that packet: it must agree.
/// if mine.digest() != packet.digest {
/// // A delta went missing. Ask for a full resync.
/// }
/// ```
/// SplitMix64's finalizer: cheap, and good enough that neighbouring keys do not
/// produce neighbouring hashes (which matters, since ids here are dense).