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}