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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use core::any::{Any, TypeId};
use std::collections::{hash_map::Entry, HashMap};

use crate::nut::IMPOSSIBLE_ERR_MSG;

/// Stores passive data that can be accessed in event handlers of multiple activities.
///
// @ START-DOC DOMAIN
/// A Domain stores arbitrary data for sharing between multiple [Activities](trait.Activity.html).
/// Library users can define the number of domains but each activity can only join one domain.
///
/// Domains should only be used when data needs to be shared between multiple activities of the same or different types.
/// If data is only used by a single activity, it is usually better to store it in the activity struct itself.
///
/// In case only one domain is used, you can also consider to use [`DefaultDomain`](struct.DefaultDomain.html) instead of creating your own enum.
///
/// For now, there is no real benefit from using multiple Domains, other than data isolation.
/// But there are plans for the future that will schedule Activities in different threads, based on their domain.
// @ END-DOC DOMAIN
#[derive(Default)]
pub struct DomainState {
    // Indirection to Vec is used here to allow for safe internal mutability without falling back to RefCells.
    // (RefCells are uneasy to use from outside AND the runtime hit is larger)
    objects: Vec<Box<dyn Any>>,
    index_map: HashMap<TypeId, usize>,
}

impl DomainState {
    /// Stores a value in the domain.
    // @ START-DOC DOMAIN_STORE
    /// Only one instance per type id can be stored inside a domain.
    /// If an old value of the same type already exists in the domain, it will be overwritten.
    // @ END-DOC DOMAIN_STORE
    pub fn store<T: Any>(&mut self, obj: T) {
        let id = TypeId::of::<T>();
        match self.index_map.entry(id) {
            Entry::Occupied(entry) => {
                *self.objects[*entry.get()]
                    .downcast_mut()
                    .expect(IMPOSSIBLE_ERR_MSG) = obj;
            }
            Entry::Vacant(entry) => {
                entry.insert(self.objects.len());
                self.objects.push(Box::new(obj));
            }
        }
    }
    /// For internal use only.
    ///
    /// Non-generic variant of store.
    /// Used for delayed stores to domains.
    ///
    /// This variant is slightly less efficient as it will allocate another Box if the value was already in the domain.
    pub(crate) fn store_unchecked(&mut self, id: TypeId, obj: Box<dyn Any>) {
        match self.index_map.entry(id) {
            Entry::Occupied(entry) => {
                self.objects[*entry.get()] = obj;
            }
            Entry::Vacant(entry) => {
                entry.insert(self.objects.len());
                self.objects.push(obj);
            }
        }
    }
    /// Returns a reference to a value of the specified type, if such a value has previously been stored to the domain.
    #[allow(clippy::unwrap_used)]
    pub fn try_get<T: Any>(&self) -> Option<&T> {
        self.index_map
            .get(&TypeId::of::<T>())
            .map(|index| self.objects[*index].as_ref().downcast_ref().unwrap())
    }
    /// Same as [`try_get`](#try_get) but grants mutable access to the object.
    #[allow(clippy::unwrap_used)]
    pub fn try_get_mut<T: Any>(&mut self) -> Option<&mut T> {
        if let Some(index) = self.index_map.get(&TypeId::of::<T>()) {
            Some(self.objects[*index].as_mut().downcast_mut().unwrap())
        } else {
            None
        }
    }
    /// Return two mutable references to domain objects
    #[allow(clippy::unwrap_used)]
    pub fn try_get_2_mut<T1: Any, T2: Any>(&mut self) -> (Option<&mut T1>, Option<&mut T2>) {
        let type_1: TypeId = TypeId::of::<T1>();
        let type_2: TypeId = TypeId::of::<T2>();
        assert_ne(type_1, type_2);
        let i1 = self.index_map.get(&type_1);
        let i2 = self.index_map.get(&type_2);
        if i1.is_none() {
            return (None, self.try_get_mut());
        }
        if i2.is_none() {
            return (self.try_get_mut(), None);
        }

        let i1 = i1.unwrap();
        let i2 = i2.unwrap();

        let split = i1.min(i2) + 1;
        let (left, right) = self.objects.split_at_mut(split);

        let (t1, t2) = if i1 < i2 {
            (&mut left[*i1], &mut right[i2 - split])
        } else {
            (&mut right[i1 - split], &mut left[*i2])
        };
        (
            Some(t1.as_mut().downcast_mut().unwrap()),
            Some(t2.as_mut().downcast_mut().unwrap()),
        )
    }
    /// Returns a reference to a value of the specified type, taken from the domain.
    /// # Panics
    /// Panics if object of that type has not been stored previously.
    /// [`try_get()`](#try_get) is usually recommended instead.
    #[allow(clippy::unwrap_used)]
    pub fn get<T: Any>(&self) -> &T {
        self.try_get().expect("Not in domain")
    }
    /// Returns a mutable reference to a value of the specified type, taken from the domain.
    /// # Panics
    /// Panics if object of that type has not been stored previously
    /// [`try_get_mut()`](#try_get_mut) is usually recommended instead.
    #[allow(clippy::unwrap_used)]
    pub fn get_mut<T: Any>(&mut self) -> &mut T {
        self.try_get_mut().expect("Not in domain")
    }
}
// This should really be a const fn so that we get compile-time panic instead of run-time checks.
// But unfortunately, that is currently not possible.
fn assert_ne(t1: TypeId, t2: TypeId) {
    if t1 == t2 {
        panic!("Cannot get two mutable references of same type from domain")
    }
}