1#![forbid(unsafe_code)]
12#![warn(missing_docs)]
13
14extern crate self as arkhe_forge_core;
17
18pub mod action;
19pub mod activity;
20pub mod actor;
21pub mod brand;
22pub mod bridge;
23pub mod component;
24pub mod context;
25pub mod entry;
26pub mod event;
27pub mod pii;
28pub mod pipeline;
29#[doc(hidden)]
34#[path = "sealed.rs"]
35pub mod __sealed;
36pub mod space;
37pub mod typecode;
38pub mod user;
39
40pub use arkhe_forge_macros::{arkhe_pure, ArkheAction, ArkheComponent, ArkheEvent};
44
45use arkhe_kernel::abi::{EntityId, InstanceId, Tick, TypeCode};
46
47pub const RUNTIME_SEMVER: (u16, u16, u16) = (0, 13, 0);
49
50pub const MAX_ID_DERIVE_RETRIES: u32 = 16;
52
53#[must_use]
61pub fn derive_entity_id(
62 world_seed: &[u8; 32],
63 instance_id: InstanceId,
64 type_code: TypeCode,
65 tick: Tick,
66 seq: u32,
67) -> Option<EntityId> {
68 let domain_key = blake3::derive_key("arkhe-forge-entity-id", world_seed);
69 for bump in 0..MAX_ID_DERIVE_RETRIES {
70 let candidate_seq = seq.wrapping_add(bump);
71 let mut h = blake3::Hasher::new_keyed(&domain_key);
72 h.update(&instance_id.get().to_be_bytes());
73 h.update(&type_code.0.to_be_bytes());
74 h.update(&tick.0.to_be_bytes());
75 h.update(&candidate_seq.to_be_bytes());
76 let out = h.finalize();
77 let digest = out.as_bytes();
78 let mut first_eight = [0u8; 8];
79 first_eight.copy_from_slice(&digest[..8]);
80 let raw = u64::from_be_bytes(first_eight);
81 if let Some(id) = EntityId::new(raw) {
82 return Some(id);
83 }
84 }
85 None
86}
87
88#[cfg(test)]
89#[allow(clippy::unwrap_used, clippy::expect_used)]
90mod lib_tests {
91 use super::*;
92
93 fn instance(v: u64) -> InstanceId {
94 InstanceId::new(v).unwrap()
95 }
96
97 #[test]
98 fn derive_entity_id_is_deterministic() {
99 let seed = [0x11u8; 32];
100 let iid = instance(1);
101 let a = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(42), 0);
102 let b = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(42), 0);
103 assert_eq!(a, b);
104 assert!(a.is_some());
105 }
106
107 #[test]
108 fn derive_entity_id_differs_across_type_code() {
109 let seed = [0x11u8; 32];
110 let iid = instance(1);
111 let a = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(1), 0);
112 let b = derive_entity_id(&seed, iid, TypeCode(0x0001_0002), Tick(1), 0);
113 assert_ne!(a, b);
114 }
115
116 #[test]
117 fn derive_entity_id_differs_across_instance() {
118 let seed = [0x11u8; 32];
119 let a = derive_entity_id(&seed, instance(1), TypeCode(0x0001_0001), Tick(1), 0);
120 let b = derive_entity_id(&seed, instance(2), TypeCode(0x0001_0001), Tick(1), 0);
121 assert_ne!(a, b);
122 }
123
124 #[test]
125 fn derive_entity_id_differs_across_world_seed() {
126 let s1 = [0x01u8; 32];
127 let s2 = [0x02u8; 32];
128 let iid = instance(1);
129 let a = derive_entity_id(&s1, iid, TypeCode(0x0001_0001), Tick(1), 0);
130 let b = derive_entity_id(&s2, iid, TypeCode(0x0001_0001), Tick(1), 0);
131 assert_ne!(a, b);
132 }
133
134 #[test]
135 fn derive_entity_id_seq_changes_output() {
136 let seed = [0x11u8; 32];
137 let iid = instance(1);
138 let a = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(1), 0);
139 let b = derive_entity_id(&seed, iid, TypeCode(0x0001_0001), Tick(1), 1);
140 assert_ne!(a, b);
141 }
142}