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
use datafrost::*;
use std::ops::*;
/// First, we define a general "kind" of data that our program will use.
/// In this case, let's imagine that we want to efficiently deal with
/// arrays of numbers.
pub struct NumberArray;
/// Defines the layout of an array of numbers.
pub struct NumberArrayDescriptor {
/// The length of the array.
pub len: usize,
}
impl Kind for NumberArray {
type FormatDescriptor = NumberArrayDescriptor;
type UsageDescriptor = Range<usize>;
}
/// Next, we define the primary data format that we would like
/// to use and modify - an array of specifically `u32`s.
pub struct PrimaryArray(Vec<u32>);
impl Format for PrimaryArray {
type Kind = NumberArray;
fn allocate(descriptor: &NumberArrayDescriptor) -> Self {
Self(vec![0; descriptor.len])
}
}
/// Now, let's imagine that we want to efficiently maintain an
/// acceleration structure containing all of the numbers in
/// the array, but doubled. So, we define the format.
pub struct DoubledArray(Vec<u32>);
impl Format for DoubledArray {
type Kind = NumberArray;
fn allocate(descriptor: &NumberArrayDescriptor) -> Self {
Self(vec![0; descriptor.len])
}
}
/// Our goal is for `datafrost` to automatically update the doubled
/// array whenever the primary array changes. Thus, we implement
/// a way for it do so.
pub struct DoublePrimaryArray;
impl DerivedDescriptor<PrimaryArray> for DoublePrimaryArray {
type Format = DoubledArray;
fn update(&self, data: &mut DoubledArray, parent: &PrimaryArray, usages: &[&Range<usize>]) {
// Loop over all ranges of the array that have changed, and
// for each value in the range, recompute the data.
for range in usages.iter().copied() {
for i in range.clone() {
data.0[i] = 2 * parent.0[i];
}
}
}
}
/// Once our data formats are defined, we can create and use them with
/// a `DataFrostContext`. We can schedule commands to asynchronously
/// execute against the context and they will execute in parallel.
fn main() {
// Create a new context.
let ctx = DataFrostContext::new(ContextDescriptor {
label: Some("my context"),
});
// Allocate a new primary array object, which has a doubled
// array as a derived format.
let data = ctx.allocate::<PrimaryArray>(AllocationDescriptor {
descriptor: NumberArrayDescriptor { len: 7 },
label: Some("my data"),
derived_formats: &[Derived::new(DoublePrimaryArray)],
});
// Create a command buffer to record operations to execute
// on our data.
let mut command_buffer = CommandBuffer::new(CommandBufferDescriptor {
label: Some("my command buffer"),
});
// Schedule a command to fill the primary number array with some data.
let view = data.view::<PrimaryArray>();
let view_clone = view.clone();
command_buffer.schedule(CommandDescriptor {
label: Some("fill array"),
views: &[&view.as_mut(4..6)],
command: move |ctx| ctx.get_mut(&view_clone).0[4..6].fill(33),
});
// Schedule a command to map the contents of the derived acceleration structure
// so that we may view them synchronously.
let derived = command_buffer.map(&data.view::<DoubledArray>().as_const());
// Submit the buffer for processing.
ctx.submit(command_buffer);
// The doubled acceleration structure automatically contains the
// correct, up-to-date data!
assert_eq!(&[0, 0, 0, 0, 66, 66, 0], &ctx.get(&derived).0[..]);
}