1use std::convert::TryFrom;
4use std::fmt::Debug;
5use std::hash::{Hash, Hasher};
6use std::mem;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Mutex;
9
10use crate::coretypes::{Cp, Move, MoveInfo, PieceKind::*, PlyKind, Square};
11use crate::position::{Cache, Position};
12use crate::zobrist::{HashKind, ZobristTable};
13
14#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
17#[repr(u8)]
18pub enum NodeKind {
19 All,
21 Cut,
23 Pv,
25}
26
27impl TryFrom<u8> for NodeKind {
28 type Error = ();
29 fn try_from(value: u8) -> Result<Self, Self::Error> {
30 const ALL: u8 = NodeKind::All as u8;
31 const CUT: u8 = NodeKind::Cut as u8;
32 const PV: u8 = NodeKind::Pv as u8;
33
34 match value {
35 ALL => Ok(NodeKind::All),
36 CUT => Ok(NodeKind::Cut),
37 PV => Ok(NodeKind::Pv),
38 _ => Err(()),
39 }
40 }
41}
42
43#[derive(Debug, Copy, Clone, Eq, PartialEq)]
45pub struct Entry {
46 pub hash: HashKind,
48 pub key_move: Move,
50 pub score: Cp,
52 pub ply: PlyKind,
54 pub node_kind: NodeKind,
56}
57
58impl Entry {
59 pub fn new(
61 hash: HashKind,
62 key_move: Move,
63 score: Cp,
64 ply: PlyKind,
65 node_kind: NodeKind,
66 ) -> Self {
67 Self {
68 hash,
69 key_move,
70 score,
71 ply,
72 node_kind,
73 }
74 }
75
76 pub fn illegal() -> Self {
78 Self {
79 hash: 0,
80 key_move: Move::illegal(),
81 score: Cp(0),
82 ply: 0,
83 node_kind: NodeKind::All,
84 }
85 }
86}
87
88impl Hash for Entry {
89 fn hash<H: Hasher>(&self, h: &mut H) {
90 h.write_u64(self.hash)
91 }
92}
93
94impl Default for Entry {
95 fn default() -> Self {
96 Self::illegal()
97 }
98}
99
100pub trait TwoBucket: Debug + Default + Sync {
103 fn len() -> usize {
105 2
106 }
107
108 fn get(&self, hash: HashKind) -> Option<Entry>;
111
112 fn contains(&self, hash: HashKind) -> bool;
114
115 fn store(&self, general_entry: Entry);
117
118 fn replace(&self, priority_entry: Entry, age: u8);
120
121 fn swap_replace(&self, priority_entry: Entry, age: u8);
124
125 fn replace_by<F>(&self, entry: Entry, age: u8, should_replace: F)
139 where
140 F: FnOnce(&Entry, u8, &Entry, u8) -> bool;
141
142 fn swap_replace_by<F>(&self, entry: Entry, age: u8, should_replace: F)
157 where
158 F: FnOnce(&Entry, u8, &Entry, u8) -> bool;
159}
160
161#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
164pub struct DummyBucket;
165
166impl TwoBucket for DummyBucket {
167 fn get(&self, _hash: HashKind) -> Option<Entry> {
168 None
169 }
170 fn contains(&self, _hash: HashKind) -> bool {
171 false
172 }
173 fn store(&self, _general_entry: Entry) {}
174 fn replace(&self, _priority_entry: Entry, _age: u8) {}
175 fn swap_replace(&self, _priority_entry: Entry, _age: u8) {}
176 fn replace_by<F>(&self, _entry: Entry, _age: u8, _should_replace: F)
177 where
178 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
179 {
180 }
181 fn swap_replace_by<F>(&self, _entry: Entry, _age: u8, _should_replace: F)
182 where
183 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
184 {
185 }
186}
187
188pub type AgeKind = u8;
190
191#[derive(Debug, Copy, Clone, Eq, PartialEq)]
198struct LockInner {
199 pub priority: Entry,
201 pub general: Entry,
203 pub age: AgeKind,
205}
206
207impl LockInner {
208 #[inline]
210 fn inner_replace(&mut self, priority_entry: Entry, age: AgeKind) {
211 self.priority = priority_entry;
212 self.age = age;
213 }
214
215 #[inline]
216 fn inner_swap_replace(&mut self, priority_entry: Entry, age: AgeKind) {
217 self.general = mem::replace(&mut self.priority, priority_entry);
218 self.age = age;
219 }
220
221 #[inline]
222 fn inner_store(&mut self, general_entry: Entry) {
223 self.general = general_entry;
224 }
225}
226
227#[derive(Debug)]
229pub struct LockBucket {
230 mu: Mutex<LockInner>,
231}
232
233impl LockBucket {
234 fn illegal() -> Self {
236 LockBucket {
237 mu: Mutex::new(LockInner {
238 age: 0,
239 priority: Entry::illegal(),
240 general: Entry::illegal(),
241 }),
242 }
243 }
244}
245
246impl Default for LockBucket {
247 fn default() -> Self {
248 Self::illegal()
249 }
250}
251
252impl TwoBucket for LockBucket {
253 #[inline]
254 fn get(&self, hash: HashKind) -> Option<Entry> {
255 let inner: LockInner = { *self.mu.lock().unwrap() };
256
257 if inner.priority.hash == hash {
258 Some(inner.priority)
259 } else if inner.general.hash == hash {
260 Some(inner.general)
261 } else {
262 None
263 }
264 }
265
266 #[inline]
267 fn contains(&self, hash: HashKind) -> bool {
268 let (priority_hash, general_hash) = {
269 let lock = self.mu.lock().unwrap();
270 (lock.priority.hash, lock.general.hash)
271 };
272 priority_hash == hash || general_hash == hash
273 }
274
275 #[inline]
276 fn store(&self, general_entry: Entry) {
277 let mut lock = self.mu.lock().unwrap();
278 lock.inner_store(general_entry);
279 }
280
281 #[inline]
282 fn replace(&self, priority_entry: Entry, age: AgeKind) {
283 let mut lock = self.mu.lock().unwrap();
284 lock.inner_replace(priority_entry, age);
285 }
286
287 #[inline]
288 fn swap_replace(&self, priority_entry: Entry, age: AgeKind) {
289 let mut lock = self.mu.lock().unwrap();
290 lock.inner_swap_replace(priority_entry, age);
291 }
292
293 #[inline]
294 fn replace_by<F>(&self, entry: Entry, age: AgeKind, should_replace: F)
295 where
296 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
297 {
298 let mut lock = self.mu.lock().unwrap();
299 match should_replace(&entry, age, &lock.priority, lock.age) {
300 true => lock.inner_replace(entry, age),
301 false => lock.inner_store(entry),
302 };
303 }
304
305 #[inline]
306 fn swap_replace_by<F>(&self, entry: Entry, age: AgeKind, should_replace: F)
307 where
308 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
309 {
310 let mut lock = self.mu.lock().unwrap();
311 match should_replace(&entry, age, &lock.priority, lock.age) {
312 true => lock.inner_swap_replace(entry, age),
313 false => lock.inner_store(entry),
314 };
315 }
316}
317
318#[rustfmt::skip]
339#[allow(dead_code)] mod adf {
341 pub const FROM_MASK: u64 = 0x00000000000000FF;
342 pub const TO_MASK: u64 = 0x000000000000FF00;
343 pub const PROMOTION_MASK: u64 = 0x0000000000FF0000;
344 pub const SCORE_MASK: u64 = 0x000000FFFF000000;
345 pub const PLY_MASK: u64 = 0x0000FF0000000000;
346 pub const NODE_KIND_MASK: u64 = 0x00FF000000000000;
347 pub const AGE_MASK: u64 = 0xFF00000000000000;
348
349 pub const FROM_SHIFT: u8 = 0;
350 pub const TO_SHIFT: u8 = 8;
351 pub const PROMOTION_SHIFT: u8 = 16;
352 pub const SCORE_SHIFT: u8 = 24;
353 pub const PLY_SHIFT: u8 = 40;
354 pub const NODE_KIND_SHIFT: u8 = 48;
355 pub const AGE_SHIFT: u8 = 56;
356
357 pub const FROM_BYTES: usize = 1;
358 pub const TO_BYTES: usize = 1;
359 pub const PROMOTION_BYTES: usize = 1;
360 pub const SCORE_BYTES: usize = 2;
361 pub const PLY_BYTES: usize = 1;
362 pub const NODE_KIND_BYTES: usize = 1;
363 pub const AGE_BYTES: usize = 1;
364 pub const OPT_PIECE_KIND_BYTES: usize = 1;
365 pub const SQUARE_BYTES: usize = 1;
366}
367
368#[derive(Debug)]
371pub struct AtomicEntry {
372 data: AtomicU64,
374 hash_xor_data: AtomicU64,
376}
377
378impl AtomicEntry {
379 fn load(&self, ordering: Ordering) -> LoadedAtomicEntry {
381 LoadedAtomicEntry {
382 data: self.data.load(ordering),
383 hash_xor_data: self.hash_xor_data.load(ordering),
384 }
385 }
386
387 fn store(&self, loaded_entry: LoadedAtomicEntry, ordering: Ordering) {
389 self.data.store(loaded_entry.data, ordering);
390 self.hash_xor_data
391 .store(loaded_entry.hash_xor_data, ordering);
392 }
393}
394
395impl From<LoadedAtomicEntry> for AtomicEntry {
396 fn from(loaded_entry: LoadedAtomicEntry) -> Self {
397 Self {
398 data: AtomicU64::new(loaded_entry.data),
399 hash_xor_data: AtomicU64::new(loaded_entry.hash_xor_data),
400 }
401 }
402}
403
404impl Default for AtomicEntry {
405 fn default() -> Self {
406 Self::from(LoadedAtomicEntry::default())
407 }
408}
409
410#[derive(Debug, Copy, Clone, Eq, PartialEq)]
412struct LoadedAtomicEntry {
413 data: u64,
414 hash_xor_data: u64,
415}
416
417impl LoadedAtomicEntry {
418 const fn hash(&self) -> HashKind {
420 self.data ^ self.hash_xor_data
421 }
422
423 fn entry(&self) -> Entry {
425 self.unpack().0
426 }
427
428 #[inline]
429 fn pack_move(move_: Move) -> u64 {
430 let from_u8 = move_.from as u8;
431 let to_u8 = move_.to as u8;
432 let promotion_u8 = match move_.promotion {
433 None => 0,
434 Some(King) => 1,
435 Some(Pawn) => 2,
436 Some(Knight) => 3,
437 Some(Rook) => 4,
438 Some(Queen) => 5,
439 Some(Bishop) => 6,
440 };
441
442 let from_pack = Self::pack_u8(from_u8, adf::FROM_SHIFT);
443 let to_pack = Self::pack_u8(to_u8, adf::TO_SHIFT);
444 let promotion_pack = Self::pack_u8(promotion_u8, adf::PROMOTION_SHIFT);
445 from_pack | to_pack | promotion_pack
446 }
447
448 fn unpack_move(packed: u64) -> Move {
450 let from_u8 = Self::unpack_u8(packed, adf::FROM_SHIFT, adf::FROM_MASK);
451 let to_u8 = Self::unpack_u8(packed, adf::TO_SHIFT, adf::TO_MASK);
452 let promo_u8 = Self::unpack_u8(packed, adf::PROMOTION_SHIFT, adf::PROMOTION_MASK);
453
454 let from = Square::try_from(from_u8).unwrap();
455 let to = Square::try_from(to_u8).unwrap();
456 let promotion = match promo_u8 {
457 0 => None,
458 1 => Some(King),
459 2 => Some(Pawn),
460 3 => Some(Knight),
461 4 => Some(Rook),
462 5 => Some(Queen),
463 6 => Some(Bishop),
464 _ => None,
465 };
466
467 Move::new(from, to, promotion)
468 }
469
470 #[inline]
471 const fn pack_i16(value: i16, shift: u8, mask: u64) -> u64 {
472 ((value as u64) << shift) & mask
473 }
474
475 #[inline]
477 const fn unpack_i16(packed_data: u64, shift: u8, mask: u64) -> i16 {
478 ((packed_data & mask) >> shift) as i16
479 }
480
481 #[inline]
482 const fn pack_u8(value: u8, shift: u8) -> u64 {
483 (value as u64) << shift
484 }
485
486 #[inline]
487 const fn unpack_u8(packed_data: u64, shift: u8, mask: u64) -> u8 {
488 ((packed_data & mask) >> shift) as u8
489 }
490
491 fn pack(&mut self, entry: Entry, age: u8) {
493 let hash = entry.hash;
494 let mut data: u64 = 0;
495
496 data |= Self::pack_move(entry.key_move);
497 data |= Self::pack_i16(entry.score.0, adf::SCORE_SHIFT, adf::SCORE_MASK);
498 data |= Self::pack_u8(entry.ply, adf::PLY_SHIFT);
499 data |= Self::pack_u8(entry.node_kind as u8, adf::NODE_KIND_SHIFT);
500 data |= Self::pack_u8(age, adf::AGE_SHIFT);
501 self.data = data;
502 self.hash_xor_data = hash ^ data;
503 }
504
505 fn unpack(&self) -> (Entry, AgeKind) {
507 let data = self.data;
508 let hash_xor_data = self.hash_xor_data;
509 let hash: u64 = data ^ hash_xor_data;
510
511 let key_move = Self::unpack_move(data);
512 let score = Cp(Self::unpack_i16(data, adf::SCORE_SHIFT, adf::SCORE_MASK));
513 let ply: PlyKind = Self::unpack_u8(data, adf::PLY_SHIFT, adf::PLY_MASK);
514 let node_kind = NodeKind::try_from(Self::unpack_u8(
515 data,
516 adf::NODE_KIND_SHIFT,
517 adf::NODE_KIND_MASK,
518 ))
519 .unwrap();
520
521 let age: AgeKind = Self::unpack_u8(data, adf::AGE_SHIFT, adf::AGE_MASK);
522 let entry = Entry::new(hash, key_move, score, ply, node_kind);
523 (entry, age)
524 }
525}
526
527impl Default for LoadedAtomicEntry {
528 fn default() -> Self {
529 Self::from(Entry::illegal())
530 }
531}
532
533impl From<Entry> for LoadedAtomicEntry {
534 fn from(entry: Entry) -> Self {
535 let mut loaded_atomic_entry = LoadedAtomicEntry {
536 data: 0,
537 hash_xor_data: 0,
538 };
539 loaded_atomic_entry.pack(entry, 0);
540 loaded_atomic_entry
541 }
542}
543
544impl From<(Entry, AgeKind)> for LoadedAtomicEntry {
545 fn from((entry, age): (Entry, AgeKind)) -> Self {
546 let mut loaded_atomic_entry = LoadedAtomicEntry {
547 data: 0,
548 hash_xor_data: 0,
549 };
550 loaded_atomic_entry.pack(entry, age);
551 loaded_atomic_entry
552 }
553}
554
555#[derive(Debug, Default)]
557pub struct AtomicBucket {
558 priority: AtomicEntry,
559 general: AtomicEntry,
560}
561
562impl TwoBucket for AtomicBucket {
563 fn get(&self, hash: HashKind) -> Option<Entry> {
564 let loaded_priority = self.priority.load(Ordering::Acquire);
565 let loaded_general = self.general.load(Ordering::Acquire);
566
567 if hash == loaded_priority.hash() {
568 Some(loaded_priority.entry())
569 } else if hash == loaded_general.hash() {
570 Some(loaded_general.entry())
571 } else {
572 None
573 }
574 }
575
576 fn contains(&self, hash: HashKind) -> bool {
578 let loaded_priority = self.priority.load(Ordering::Acquire);
579 let loaded_general = self.general.load(Ordering::Acquire);
580 hash == loaded_priority.hash() || hash == loaded_general.hash()
581 }
582
583 fn store(&self, general_entry: Entry) {
585 self.general.store(general_entry.into(), Ordering::Release);
586 }
587
588 fn replace(&self, priority_entry: Entry, age: u8) {
590 self.priority
591 .store((priority_entry, age).into(), Ordering::Release);
592 }
593
594 fn swap_replace(&self, priority_entry: Entry, age: u8) {
597 let new_general = self.priority.load(Ordering::Acquire);
598 self.replace(priority_entry, age);
599 self.general.store(new_general, Ordering::Release);
600 }
601
602 fn replace_by<F>(&self, entry: Entry, age: u8, should_replace: F)
616 where
617 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
618 {
619 let priority = self.priority.load(Ordering::Acquire);
620 let (existing_entry, existing_age) = priority.unpack();
621
622 match should_replace(&entry, age, &existing_entry, existing_age) {
623 true => self.replace(entry, age),
624 false => self.store(entry),
625 }
626 }
627
628 fn swap_replace_by<F>(&self, entry: Entry, age: u8, should_replace: F)
643 where
644 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
645 {
646 let priority = self.priority.load(Ordering::Acquire);
647 let (existing_entry, existing_age) = priority.unpack();
648
649 if should_replace(&entry, age, &existing_entry, existing_age) {
650 self.replace(entry, age);
651 self.general.store(priority, Ordering::Release);
652 } else {
653 self.store(entry);
654 }
655 }
656}
657
658fn fill_with_default<Bucket: TwoBucket>(v: &mut Vec<Bucket>) {
660 let capacity = v.capacity();
661 while v.len() < capacity {
662 v.push(Bucket::default());
663 }
664 debug_assert_eq!(v.len(), capacity);
665 debug_assert_eq!(v.capacity(), capacity);
666}
667
668pub struct TranspositionTable<Bucket: TwoBucket = AtomicBucket> {
694 bucket_capacity: usize,
696 ztable: ZobristTable,
698 transpositions: Vec<Bucket>,
700}
701
702impl TranspositionTable {
704 pub fn new() -> Self {
706 Self::new_in()
707 }
708
709 pub fn with_capacity(entry_capacity: usize) -> Self {
712 Self::with_capacity_in(entry_capacity)
713 }
714
715 pub fn with_mb(mb: usize) -> Self {
718 Self::with_mb_in(mb)
719 }
720
721 pub fn with_zobrist(ztable: ZobristTable) -> Self {
724 Self::with_zobrist_in(ztable)
725 }
726
727 pub fn with_mb_and_zobrist(mb: usize, ztable: ZobristTable) -> Self {
730 Self::with_mb_and_zobrist_in(mb, ztable)
731 }
732
733 pub fn with_capacity_and_zobrist(entry_capacity: usize, ztable: ZobristTable) -> Self {
736 Self::with_capacity_and_zobrist_in(entry_capacity, ztable)
737 }
738}
739
740impl<Bucket: TwoBucket> TranspositionTable<Bucket> {
742 const DEFAULT_MAX_ENTRIES: usize = 100_000;
744
745 fn mb_to_bucket_capacity(mb: usize) -> usize {
747 assert!(mb > 0, "mb cannot be 0");
748 (mb * 1_000_000) / mem::size_of::<Bucket>()
749 }
750
751 fn mb_to_entry_capacity(mb: usize) -> usize {
752 assert!(mb > 0, "mb cannot be 0");
753 let bucket_capacity = Self::mb_to_bucket_capacity(mb);
754 bucket_capacity * Bucket::len()
755 }
756
757 pub fn zobrist_table(&self) -> &ZobristTable {
759 &self.ztable
760 }
761
762 pub fn new_in() -> Self {
765 let ztable = ZobristTable::new();
766 Self::with_capacity_and_zobrist_in(Self::DEFAULT_MAX_ENTRIES, ztable)
767 }
768
769 pub fn with_capacity_in(entry_capacity: usize) -> Self {
772 let ztable = ZobristTable::new();
773 Self::with_capacity_and_zobrist_in(entry_capacity, ztable)
774 }
775
776 pub fn with_mb_in(mb: usize) -> Self {
779 let entry_capacity = Self::mb_to_entry_capacity(mb);
780 let ztable = ZobristTable::new();
781 Self::with_capacity_and_zobrist_in(entry_capacity, ztable)
782 }
783
784 pub fn with_zobrist_in(ztable: ZobristTable) -> Self {
787 let entry_capacity = Self::DEFAULT_MAX_ENTRIES;
788 Self::with_capacity_and_zobrist_in(entry_capacity, ztable)
789 }
790
791 pub fn with_mb_and_zobrist_in(mb: usize, ztable: ZobristTable) -> Self {
793 let entry_capacity = Self::mb_to_entry_capacity(mb);
794 Self::with_capacity_and_zobrist_in(entry_capacity, ztable)
795 }
796
797 pub fn with_capacity_and_zobrist_in(entry_capacity: usize, ztable: ZobristTable) -> Self {
800 let bucket_capacity = (entry_capacity + Bucket::len() - 1) / Bucket::len();
802
803 let mut transpositions = Vec::with_capacity(bucket_capacity);
804 fill_with_default(&mut transpositions);
805
806 assert_eq!(bucket_capacity, transpositions.capacity());
807 assert_eq!(bucket_capacity, transpositions.len());
808 Self {
809 bucket_capacity,
810 ztable,
811 transpositions,
812 }
813 }
814
815 pub fn capacity(&self) -> usize {
817 assert_eq!(self.bucket_capacity, self.transpositions.capacity());
818 self.transpositions.capacity() * Bucket::len()
819 }
820
821 pub fn bucket_capacity(&self) -> usize {
823 assert_eq!(self.bucket_capacity, self.transpositions.capacity());
824 self.bucket_capacity
825 }
826
827 pub fn clear(&mut self) {
831 for bucket in &mut self.transpositions {
832 *bucket = Bucket::default();
833 }
834 debug_assert_eq!(self.bucket_capacity, self.transpositions.capacity());
835 debug_assert_eq!(self.bucket_capacity, self.transpositions.len());
836 }
837
838 pub fn set_mb(&mut self, new_mb: usize) -> usize {
842 let entry_capacity = Self::mb_to_entry_capacity(new_mb);
843 let ztable = self.ztable.clone();
844 *self = Self::with_capacity_and_zobrist_in(entry_capacity, ztable);
845 self.capacity()
846 }
847
848 pub fn generate_hash(&self, position: &Position) -> HashKind {
852 self.ztable.generate_hash(position.into())
853 }
854
855 pub fn update_hash(
857 &self,
858 hash: &mut HashKind,
859 position: &Position,
860 move_info: MoveInfo,
861 cache: Cache,
862 ) {
863 self.ztable
864 .update_hash(hash, position.into(), move_info, cache);
865 }
866
867 pub fn update_from_hash(
869 &self,
870 mut hash: HashKind,
871 position: &Position,
872 move_info: MoveInfo,
873 cache: Cache,
874 ) -> HashKind {
875 self.ztable
876 .update_hash(&mut hash, position.into(), move_info, cache);
877 hash
878 }
879
880 pub fn hash_to_index(&self, hash: HashKind) -> usize {
882 (hash % self.bucket_capacity as HashKind) as usize
883 }
884
885 pub fn contains(&self, hash: HashKind) -> bool {
889 let index = self.hash_to_index(hash);
890 self.transpositions[index].contains(hash)
891 }
892
893 pub fn get(&self, hash: HashKind) -> Option<Entry> {
895 let index = self.hash_to_index(hash);
896 self.transpositions[index].get(hash)
897 }
898
899 pub fn replace(&self, priority_entry: Entry, age: AgeKind) {
903 let index = self.hash_to_index(priority_entry.hash);
904 self.transpositions[index].replace(priority_entry, age);
905
906 debug_assert_eq!(self.bucket_capacity, self.transpositions.capacity());
907 debug_assert_eq!(self.bucket_capacity, self.transpositions.len());
908 }
909
910 pub fn swap_replace(&self, priority_entry: Entry, age: AgeKind) {
912 let index = self.hash_to_index(priority_entry.hash);
913 self.transpositions[index].swap_replace(priority_entry, age);
914 }
915
916 pub fn store(&self, general_entry: Entry) {
918 let index = self.hash_to_index(general_entry.hash);
919 self.transpositions[index].store(general_entry);
920 }
921
922 pub fn replace_by<F>(&self, entry: Entry, age: AgeKind, should_replace: F)
970 where
971 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
972 {
973 let index = self.hash_to_index(entry.hash);
974 self.transpositions[index].replace_by(entry, age, should_replace);
975 }
976
977 pub fn swap_replace_by<F>(&self, entry: Entry, age: AgeKind, should_replace: F)
981 where
982 F: FnOnce(&Entry, u8, &Entry, u8) -> bool,
983 {
984 let index = self.hash_to_index(entry.hash);
985 self.transpositions[index].swap_replace_by(entry, age, should_replace)
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use super::*;
992 use crate::coretypes::{PieceKind, Square::*};
993 use std::mem::size_of;
994
995 #[test]
996 fn atomic_pack_sizes() {
997 assert_eq!(adf::FROM_BYTES, size_of::<Square>());
1001 assert_eq!(adf::TO_BYTES, size_of::<Square>());
1002 assert_eq!(adf::PROMOTION_BYTES, size_of::<Option<PieceKind>>());
1003 assert_eq!(adf::SCORE_BYTES, size_of::<Cp>());
1004 assert_eq!(adf::PLY_BYTES, size_of::<PlyKind>());
1005 assert_eq!(adf::NODE_KIND_BYTES, size_of::<NodeKind>());
1006 assert_eq!(adf::AGE_BYTES, size_of::<AgeKind>());
1007 }
1008
1009 #[test]
1010 fn loaded_atomic_entry() {
1011 {
1012 let entry = Entry::illegal();
1014 let loaded = LoadedAtomicEntry::from(entry);
1015 assert_eq!(entry.hash, loaded.hash());
1016 assert_eq!(entry, loaded.entry());
1017 }
1018 {
1019 let entry = Entry::new(500, Move::new(D2, D4, None), Cp(5000), 5, NodeKind::Pv);
1021 let age: AgeKind = 7;
1022
1023 let loaded = LoadedAtomicEntry::from((entry, age));
1024 let (loaded_entry, loaded_age) = loaded.unpack();
1025 assert_eq!(entry, loaded_entry);
1026 assert_eq!(age, loaded_age);
1027 }
1028 {
1029 let entry = Entry::new(
1031 0xAAFFEE,
1032 Move::new(H7, H8, Some(Knight)),
1033 Cp(-51),
1034 10,
1035 NodeKind::Cut,
1036 );
1037 let age: AgeKind = 7;
1038
1039 let loaded = LoadedAtomicEntry::from((entry, age));
1040 let (loaded_entry, loaded_age) = loaded.unpack();
1041 assert_eq!(entry, loaded_entry);
1042 assert_eq!(age, loaded_age);
1043 }
1044 }
1045
1046 #[test]
1058 fn new_tt_no_panic() {
1059 let hash: HashKind = 100;
1060 let tt = TranspositionTable::new();
1061 let tt_entry = Entry {
1062 hash,
1063 node_kind: NodeKind::All,
1064 key_move: Move::new(A2, A3, None),
1065 ply: 3,
1066 score: Cp(100),
1067 };
1068
1069 tt.store(tt_entry);
1070 assert!(tt.contains(hash));
1071 }
1072
1073 #[test]
1074 fn tt_single_capacity_replaces() {
1075 let tt = TranspositionTable::with_capacity(1);
1076 let age = 1;
1077 let tt_entry1 = Entry {
1078 hash: 100,
1079 node_kind: NodeKind::All,
1080 key_move: Move::new(A2, A3, None),
1081 ply: 3,
1082 score: Cp(100),
1083 };
1084 let tt_entry2 = Entry {
1085 hash: 200,
1086 node_kind: NodeKind::All,
1087 key_move: Move::new(B5, B3, None),
1088 ply: 4,
1089 score: Cp(-200),
1090 };
1091
1092 assert!(!tt.contains(tt_entry1.hash));
1094 assert!(!tt.contains(tt_entry2.hash));
1095 assert_eq!(tt.get(tt_entry1.hash), None);
1096 assert_eq!(tt.get(tt_entry2.hash), None);
1097
1098 tt.replace(tt_entry1, age);
1100 assert!(tt.contains(tt_entry1.hash));
1101 assert!(!tt.contains(tt_entry2.hash));
1102 assert_eq!(tt.get(tt_entry1.hash), Some(tt_entry1));
1103 assert_eq!(tt.get(tt_entry2.hash), None);
1104
1105 tt.replace(tt_entry2, age);
1107 assert!(!tt.contains(tt_entry1.hash));
1108 assert!(tt.contains(tt_entry2.hash));
1109 assert_eq!(tt.get(tt_entry1.hash), None);
1110 assert_eq!(tt.get(tt_entry2.hash), Some(tt_entry2));
1111 }
1112
1113 #[test]
1114 fn tt_start_position() {
1115 let tt = TranspositionTable::with_capacity(10000);
1116 let pos = Position::start_position();
1117 let hash = tt.generate_hash(&pos);
1118 let age = 1;
1119 let tt_entry = Entry {
1120 hash,
1121 node_kind: NodeKind::All,
1122 key_move: Move::new(D2, D4, None),
1123 ply: 5,
1124 score: Cp(0),
1125 };
1126
1127 assert!(!tt.contains(hash));
1129 assert_eq!(tt.get(hash), None);
1130
1131 tt.replace(tt_entry, age);
1133 assert!(tt.contains(hash));
1134 assert_eq!(tt.get(hash), Some(tt_entry));
1135 }
1136}