Skip to content

Commit

Permalink
Merge pull request #29 from pragmatrix/generic-dependency-tracking
Browse files Browse the repository at this point in the history
Generic dependency tracking.
  • Loading branch information
pragmatrix committed Jul 2, 2024
2 parents b0ce721 + 7da263c commit 9c753f5
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 55 deletions.
87 changes: 87 additions & 0 deletions renderer/src/scene/dependency_resolver.rs
Original file line number Diff line number Diff line change
@@ -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<Resolver: DependencyResolver>(
head_version: Version,
shared_storage: &Resolver::SharedStorage,
computed_storage: &mut Resolver::ComputedStorage,
id: Id,
) where
Computed<Resolver::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<Self::Source>;

/// Make sure that all dependencies are up to date and return their maximum version.
fn resolve_dependencies(
head_version: Version,
source: &Versioned<Self::Source>,
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<Self::Computed>
where
Computed<Self::Computed>: Default;
}
4 changes: 2 additions & 2 deletions renderer/src/scene/id_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl<T> IdTable<T> {
/// 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,
{
Expand All @@ -42,7 +42,7 @@ impl<T> IdTable<T> {
self.rows.resize_with(index + 1, || T::default())
}

&self.rows[index]
&mut self.rows[index]
}

pub fn iter(&self) -> impl Iterator<Item = &T> {
Expand Down
100 changes: 47 additions & 53 deletions renderer/src/scene/mod.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -76,74 +79,65 @@ 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::<PositionedMatrix>(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<Self::Source> {
scene.positions.get_unwrapped(id)
}

fn resolve_dependencies(
current_version: Version,
source: &Versioned<Self::Source>,
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.

// Get the _three_ versions of the elements this one is computed on.
// 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::<Self>(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<Self::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,
};
)
}
}

Expand All @@ -158,7 +152,7 @@ impl<T> IdTable<Option<T>> {
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)
}
}
Expand All @@ -167,7 +161,7 @@ impl<T> IdTable<Option<T>> {
/// 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()
}
}
Expand Down

0 comments on commit 9c753f5

Please sign in to comment.