Cache computed physical properties
Universe properties (Forces, KineticEnergy, ...) can be expensive to compute. It would be nice to cache them.
But there are two difficult things in CS: naming and cache invalidation. Because these properties can be accessed at any point in the simulation, the Universe::step field is a very bad cache indicator. A better one will be harder to compute and use, so I will have to think wether this is worth the gain or not.
A way to do that would be to wrap the particles array in a BorrowCounter<T> struct like
struct<T> BorrowCounter<T> {
val: T,
const_counter: usize,
mut_counter: usize,
}
impl<T> BorrowCounter<T> {
pub fn borrow(&self) -> &T {
const_counter.wrapping_add(1);
&self.val
}
pub fn borrow_mut(&mut self) -> &mut T {
mut_counter.wrapping_add(1);
&mut self.val
}
pub fn const_count(&self) -> usize {self.const_counter}
pub fn mut_count(&self) -> usize {self.mut_counter}
}
Then BorrowCounter<T>::mut_count will be a good hash value.
Deref and like traits can help adding some syntactic sugar.
The borrow counter introduced in ad1f1db0f4e5e62e35444d5909859508ec85c699 was then removed in 6468edb1cf674758cb22b8cc9bdf41cc10c9559c, but this is still desirable to have. Thus reopening the issue.
The best way I see to fix this is to have a cache caching hash | data pairs, where hash is the hash of the data used by the Compute algorithm. We could add a fn hash(&self, system: &System) -> Option<u64> to the Control trait, and then use this to know whether we need to recompute some data.
impl Compute for Forces {
fn hash(&self, system: &System) -> Option<u64> {
// hash positions and cell, but not velocities
return Some(hash)
}
}
struct System {
// ....
cache: struct {
forces: struct {
data: Vec<Vector3D>,
hash: u64,
}
}
}
impl System {
fn forces(&self) -> &[Vector3D] {
if Forces.hash(self).unwrap() != self.cache.forces.hash {
self.cache.forces.data = Forces.compute(&self)
}
return &self.cache.forces;
}
}