sectorsync_core/
entity.rs1use crate::ids::{EntityHandle, EntityId, OwnerEpoch, PolicyId, StationId, Tick};
4use crate::spatial::{Bounds, Position3};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
8pub struct EntityTags(u64);
9
10impl EntityTags {
11 pub const EMPTY: Self = Self(0);
13
14 pub const fn from_bits(bits: u64) -> Self {
16 Self(bits)
17 }
18
19 pub const fn bits(self) -> u64 {
21 self.0
22 }
23
24 pub const fn contains(self, mask: Self) -> bool {
26 (self.0 & mask.0) == mask.0
27 }
28
29 pub const fn intersects(self, mask: Self) -> bool {
31 (self.0 & mask.0) != 0
32 }
33
34 pub fn insert(&mut self, mask: Self) {
36 self.0 |= mask.0;
37 }
38
39 pub fn remove(&mut self, mask: Self) {
41 self.0 &= !mask.0;
42 }
43}
44
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
47pub struct DirtyMask(u64);
48
49impl DirtyMask {
50 pub const NONE: Self = Self(0);
52 pub const TRANSFORM: Self = Self(1 << 0);
54 pub const POLICY: Self = Self(1 << 1);
56 pub const TAGS: Self = Self(1 << 2);
58 pub const CUSTOM: Self = Self(1 << 63);
60
61 pub const fn bits(self) -> u64 {
63 self.0
64 }
65
66 pub const fn contains(self, mask: Self) -> bool {
68 (self.0 & mask.0) == mask.0
69 }
70
71 pub fn insert(&mut self, mask: Self) {
73 self.0 |= mask.0;
74 }
75
76 pub fn remove(&mut self, mask: Self) {
78 self.0 &= !mask.0;
79 }
80
81 pub fn clear(&mut self) {
83 self.0 = 0;
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum EntityRole {
90 Owned {
92 owner_epoch: OwnerEpoch,
94 },
95 Ghost {
97 owner_station: StationId,
99 owner_epoch: OwnerEpoch,
101 expires_at: Tick,
103 },
104}
105
106impl EntityRole {
107 pub const fn is_owned(self) -> bool {
109 matches!(self, Self::Owned { .. })
110 }
111
112 pub const fn owner_epoch(self) -> OwnerEpoch {
114 match self {
115 Self::Owned { owner_epoch }
116 | Self::Ghost {
117 owner_epoch,
118 owner_station: _,
119 expires_at: _,
120 } => owner_epoch,
121 }
122 }
123}
124
125#[derive(Clone, Debug, PartialEq)]
127pub struct EntityRecord {
128 pub id: EntityId,
130 pub handle: EntityHandle,
132 pub position: Position3,
134 pub bounds: Bounds,
136 pub policy_id: PolicyId,
138 pub tags: EntityTags,
140 pub role: EntityRole,
142 pub dirty: DirtyMask,
144}
145
146impl EntityRecord {
147 pub fn owned(
149 id: EntityId,
150 handle: EntityHandle,
151 position: Position3,
152 bounds: Bounds,
153 policy_id: PolicyId,
154 owner_epoch: OwnerEpoch,
155 ) -> Self {
156 Self {
157 id,
158 handle,
159 position,
160 bounds,
161 policy_id,
162 tags: EntityTags::EMPTY,
163 role: EntityRole::Owned { owner_epoch },
164 dirty: DirtyMask::TRANSFORM,
165 }
166 }
167
168 #[allow(clippy::too_many_arguments)]
170 pub fn ghost(
171 id: EntityId,
172 handle: EntityHandle,
173 position: Position3,
174 bounds: Bounds,
175 policy_id: PolicyId,
176 owner_station: StationId,
177 owner_epoch: OwnerEpoch,
178 expires_at: Tick,
179 ) -> Self {
180 Self {
181 id,
182 handle,
183 position,
184 bounds,
185 policy_id,
186 tags: EntityTags::EMPTY,
187 role: EntityRole::Ghost {
188 owner_station,
189 owner_epoch,
190 expires_at,
191 },
192 dirty: DirtyMask::TRANSFORM,
193 }
194 }
195
196 pub const fn is_owned(&self) -> bool {
198 self.role.is_owned()
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn entity_tags_support_contains_intersects_and_remove() {
208 let mut tags = EntityTags::from_bits(0b1011);
209
210 assert!(tags.contains(EntityTags::from_bits(0b0011)));
211 assert!(tags.intersects(EntityTags::from_bits(0b1000)));
212 assert!(!tags.intersects(EntityTags::from_bits(0b0100_0000)));
213
214 tags.remove(EntityTags::from_bits(0b0010));
215 assert_eq!(tags.bits(), 0b1001);
216 }
217}