diff --git a/renderer/src/scene/dependency_resolver.rs b/renderer/src/scene/dependency_resolver.rs new file mode 100644 index 0000000..a7c539d --- /dev/null +++ b/renderer/src/scene/dependency_resolver.rs @@ -0,0 +1,87 @@ +use super::versioning::{Computed, Version, Versioned}; +use massive_scene::Id; + +/// Resolve a computed value. +/// +/// Invoking this function ensures that the computed value `id` is up to date with its dependencies +/// at `head_version`. +/// +/// We don't return a reference to the computed value, because the borrow checker would not be +/// able to unborrow `computed_storage` after a return (a current limitation). +/// +/// TODO: Unrecurse this. There might be degenerate cases of large dependency chains. +pub fn resolve( + head_version: Version, + shared_storage: &Resolver::SharedStorage, + computed_storage: &mut Resolver::ComputedStorage, + id: Id, +) where + Computed: Default, +{ + // Already validated at the latest version? Done. + // + // `get_or_default` must be used here. This is the only situation in which the cache may + // need to be resized. + if Resolver::computed_mut(computed_storage, id).validated_at == head_version { + return; + } + + // Save the current max dependencies version for later. + // + // In theory this could be overwritten if there are cycles in the dependency graph, but in + // practice they are not (and everything may blow up anyway). + let computed_max_deps = Resolver::computed_mut(computed_storage, id).max_deps_version; + + let source = Resolver::source(shared_storage, id); + let max_deps_version = + Resolver::resolve_dependencies(head_version, source, shared_storage, computed_storage); + + // If the max_deps_version is smaller or equal to the one of the computed value, the value is ok + // and can be marked as validated at `head_version`. + if max_deps_version <= computed_max_deps { + Resolver::computed_mut(computed_storage, id).validated_at = head_version; + return; + } + + // Compute a new value and store it. + let new_value = Resolver::compute(shared_storage, computed_storage, source); + *Resolver::computed_mut(computed_storage, id) = Computed { + validated_at: head_version, + max_deps_version, + value: new_value, + }; +} + +pub trait DependencyResolver { + /// Type of the shared table storage. + type SharedStorage; + /// Type of the computed table storage. + type ComputedStorage; + + /// The symmetric _versioned_ source value type. There must be a source value for every computed + /// value with the same id. + type Source; + /// The computed value type (must implement Default for now, use Option<> otherwise) + type Computed; + + /// Retrieve a reference to the versioned source value. + fn source(scene: &Self::SharedStorage, id: Id) -> &Versioned; + + /// Make sure that all dependencies are up to date and return their maximum version. + fn resolve_dependencies( + head_version: Version, + source: &Versioned, + shared: &Self::SharedStorage, + computed: &mut Self::ComputedStorage, + ) -> Version; + + fn compute( + shared: &Self::SharedStorage, + computed: &Self::ComputedStorage, + source: &Self::Source, + ) -> Self::Computed; + + fn computed_mut(computed: &mut Self::ComputedStorage, id: Id) -> &mut Computed + where + Computed: Default; +} diff --git a/renderer/src/scene/id_table.rs b/renderer/src/scene/id_table.rs index b251271..d02b398 100644 --- a/renderer/src/scene/id_table.rs +++ b/renderer/src/scene/id_table.rs @@ -33,7 +33,7 @@ impl IdTable { /// Returns a reference to a value at `id`. /// /// May resize and create defaults. - pub fn get_or_default(&mut self, id: Id) -> &T + pub fn mut_or_default(&mut self, id: Id) -> &mut T where T: Default, { @@ -42,7 +42,7 @@ impl IdTable { self.rows.resize_with(index + 1, || T::default()) } - &self.rows[index] + &mut self.rows[index] } pub fn iter(&self) -> impl Iterator { diff --git a/renderer/src/scene/mod.rs b/renderer/src/scene/mod.rs index 6689334..2635d98 100644 --- a/renderer/src/scene/mod.rs +++ b/renderer/src/scene/mod.rs @@ -1,11 +1,14 @@ use std::{cell::RefCell, collections::HashMap}; use euclid::num::Zero; + +use dependency_resolver::{resolve, DependencyResolver}; use id_table::IdTable; use massive_geometry::Matrix4; use massive_scene::{Change, Id, PositionRenderObj, PositionedRenderShape, SceneChange, Shape}; use versioning::{Computed, Version, Versioned}; +mod dependency_resolver; mod id_table; mod versioning; @@ -76,27 +79,31 @@ impl Scene { /// When this function returns the matrix at `position_id` is up to date with the current /// version and can be used for rendering. /// - /// We don't return a reference to the result here, because the borrow checker would make this - /// recursive function invocation unnecessarily more complex. - /// - /// TODO: Unrecurse this. There might be degenerate cases of large dependency chains. fn resolve_positioned_matrix(&self, position_id: Id, caches: &mut SceneCaches) { - let current_version = self.current_version; - // Already validated at the latest version? Done. - // - // `get_or_default` must be used here. This is the only situation in which the cache may - // need to be resized. - if caches - .positions_matrix - .get_or_default(position_id) - .validated_at - == current_version - { - return; - } + resolve::(self.current_version, self, caches, position_id); + } +} + +/// The dependency resolver for finally positioned matrix. +struct PositionedMatrix; - let position = self.positions.unwrapped(position_id); - let (parent_id, matrix) = (position.parent, position.matrix); +impl DependencyResolver for PositionedMatrix { + type SharedStorage = Scene; + type ComputedStorage = SceneCaches; + type Source = PositionRenderObj; + type Computed = Matrix4; + + fn source(scene: &Scene, id: Id) -> &Versioned { + scene.positions.get_unwrapped(id) + } + + fn resolve_dependencies( + current_version: Version, + source: &Versioned, + scene: &Scene, + caches: &mut SceneCaches, + ) -> Version { + let (parent_id, matrix_id) = (source.parent, source.matrix); // Find out the max version of all the immediate and (indirect / computed) dependencies. @@ -104,46 +111,33 @@ impl Scene { // a) The self position's version. // b) The local matrix's version. // c) The computed matrix of the parent (representing all its dependencies). - let max_deps_version = position + let max_deps_version = source .updated_at - .max(self.matrices.unwrapped(matrix).updated_at); + .max(scene.matrices.get_unwrapped(matrix_id).updated_at); // Combine with the optional parent. - let max_deps_version = { - if let Some(parent_id) = parent_id { - // Be sure the parent is up to date. - self.resolve_positioned_matrix(parent_id, caches); - caches.positions_matrix[parent_id] - .max_deps_version - .max(max_deps_version) - } else { - max_deps_version - } - }; - - // If the max_deps_version is smaller or equal to the current one, the value is ok and can - // be marked as validated for this round. - { - let positioned_matrix = &mut caches.positions_matrix[position_id]; - if max_deps_version <= positioned_matrix.max_deps_version { - positioned_matrix.validated_at = current_version; - return; - } + if let Some(parent_id) = parent_id { + // Make sure the parent is up to date. + resolve::(current_version, scene, caches, parent_id); + caches.positions_matrix[parent_id] + .max_deps_version + .max(max_deps_version) + } else { + max_deps_version } + } - // Compute a new value. + fn computed_mut(caches: &mut SceneCaches, id: Id) -> &mut Computed { + caches.positions_matrix.mut_or_default(id) + } - let local_matrix = &**self.matrices.unwrapped(matrix); - let new_value = parent_id.map_or_else( + fn compute(scene: &Scene, caches: &SceneCaches, source: &Self::Source) -> Self::Computed { + let (parent_id, matrix_id) = (source.parent, source.matrix); + let local_matrix = &**scene.matrices.get_unwrapped(matrix_id); + parent_id.map_or_else( || *local_matrix, |parent_id| *caches.positions_matrix[parent_id] * local_matrix, - ); - - caches.positions_matrix[position_id] = Computed { - validated_at: current_version, - max_deps_version, - value: new_value, - }; + ) } } @@ -158,7 +152,7 @@ impl IdTable> { Change::Create(id, value) => self.put(id, Some(value)), Change::Delete(id) => self.put(id, None), Change::Update(id, value) => { - // Already know that this index must exist, so use rows() here. + // A value at this index must exist, so use `rows_mut()` here. self.rows_mut()[*id] = Some(value) } } @@ -167,7 +161,7 @@ impl IdTable> { /// Returns a reference to the object at `id`. /// /// Panics if it does not exist. - pub fn unwrapped(&self, id: Id) -> &T { + pub fn get_unwrapped(&self, id: Id) -> &T { self[id].as_ref().unwrap() } }