use ahash::HashMap;
use re_byte_size::SizeBytes as _;
use re_log_types::StoreId;
use crate::view::system_execution_output::VisualizerViewReport;
use crate::{
AppBlueprintCtx, NeedsRepaint, SystemExecutionOutput, TimeControl, TimeControlUpdateParams,
ViewClass, ViewId, ViewState, ViewerDiagnostic, VisualizerTypeReport,
};
type ViewStateKey = (StoreId, ViewId);
#[derive(re_byte_size::SizeBytes)]
pub struct ActivePreview {
pub time_control: TimeControl,
pub play_override: Option<bool>,
pub start_point_timeline: Option<re_log_types::TimelineName>,
rendered_last_frame: bool,
}
impl Default for ActivePreview {
fn default() -> Self {
Self {
time_control: TimeControl::preview_time_control(),
play_override: None,
start_point_timeline: None,
rendered_last_frame: false,
}
}
}
#[derive(Default, re_byte_size::SizeBytes)]
pub struct PreviewState {
playing: bool,
active_previews: ahash::HashMap<StoreId, ActivePreview>,
pub requested_uris: ahash::HashSet<re_uri::DatasetUri>,
}
impl PreviewState {
pub fn playing(&self) -> bool {
self.playing
}
pub fn set_playing(&mut self, playing: bool) {
self.playing = playing;
let play_state = if playing {
re_sdk_types::blueprint::components::PlayState::Playing
} else {
re_sdk_types::blueprint::components::PlayState::Paused
};
#[expect(clippy::iter_over_hash_type)] for preview in self.active_previews.values_mut() {
preview.play_override = None;
preview
.time_control
.set_play_state(None, play_state, None::<&AppBlueprintCtx<'_>>);
}
}
pub fn register_recording(
&mut self,
store_id: &StoreId,
store_bundle: &re_entity_db::StoreBundle,
) {
self.active_previews
.entry(store_id.clone())
.or_default()
.rendered_last_frame = true;
if let Some(db) = store_bundle.get(store_id)
&& let Some(uri) = db.redap_uri()
{
self.requested_uris.remove(uri);
}
}
pub fn cleanup_recordings(&mut self, is_loaded: impl Fn(&StoreId) -> bool) {
self.active_previews.retain(|id, _| is_loaded(id));
}
pub fn tick<'db>(
&mut self,
resolve: impl Fn(&StoreId) -> Option<&'db re_entity_db::EntityDb>,
stable_dt: f32,
) -> NeedsRepaint {
let mut needs_repaint = NeedsRepaint::No;
#[expect(clippy::iter_over_hash_type)] for (id, active_preview) in &mut self.active_previews {
if !std::mem::take(&mut active_preview.rendered_last_frame) {
continue;
}
let Some(db) = resolve(id) else {
continue;
};
let res = active_preview.time_control.update(
db,
&TimeControlUpdateParams {
stable_dt,
more_data_is_streaming_in: false,
is_buffering: db.is_buffering(),
should_diff_state: false,
},
None::<&AppBlueprintCtx<'_>>,
);
needs_repaint = needs_repaint.or(res.needs_repaint);
}
needs_repaint
}
pub fn iter_active_previews(&self) -> impl Iterator<Item = (&StoreId, &ActivePreview)> {
self.active_previews.iter()
}
pub fn iter_active_previews_mut(
&mut self,
) -> impl Iterator<Item = (&StoreId, &mut ActivePreview)> {
self.active_previews.iter_mut()
}
pub fn active_preview(&self, store_id: &StoreId) -> Option<&ActivePreview> {
self.active_previews.get(store_id)
}
pub fn is_recording_playing(&self, store_id: &StoreId) -> Option<bool> {
let play_state = self.active_preview(store_id)?.time_control.play_state();
Some(play_state == re_sdk_types::blueprint::components::PlayState::Playing)
}
pub fn active_preview_mut(&mut self, store_id: &StoreId) -> Option<&mut ActivePreview> {
self.active_previews.get_mut(store_id)
}
}
#[derive(Default, re_byte_size::SizeBytes)]
pub struct ViewStates {
states: HashMap<ViewStateKey, Box<dyn ViewState>>,
visualizer_reports: HashMap<ViewStateKey, VisualizerViewReport>,
view_reports: HashMap<ViewStateKey, Vec<ViewerDiagnostic>>,
pub preview_state: Option<PreviewState>,
}
impl re_byte_size::MemUsageTreeCapture for ViewStates {
fn capture_mem_usage_tree(&self) -> re_byte_size::MemUsageTree {
let Self {
states,
visualizer_reports,
view_reports,
preview_state,
} = self;
let mut state_sizes = states
.iter()
.map(|((store_id, view_id), state)| {
(
format!("{store_id:?}/{view_id:?}"),
state.total_size_bytes(),
)
})
.collect::<Vec<_>>();
state_sizes.sort_by(|(lhs, _), (rhs, _)| lhs.cmp(rhs));
let mut states_node = re_byte_size::MemUsageNode::default();
for (name, size_bytes) in state_sizes {
states_node.add(name, size_bytes);
}
let mut node = re_byte_size::MemUsageNode::default();
node.add("states", states_node.into_tree());
node.add("visualizer_reports", visualizer_reports.heap_size_bytes());
node.add("view_reports", view_reports.heap_size_bytes());
node.add("preview", preview_state.heap_size_bytes());
node.with_total_size_bytes(self.total_size_bytes())
}
}
impl ViewStates {
pub fn get(&self, store_id: &StoreId, view_id: ViewId) -> Option<&dyn ViewState> {
self.states
.get(&(store_id.clone(), view_id))
.map(|s| s.as_ref())
}
pub fn get_mut_or_create(
&mut self,
store_id: &StoreId,
view_id: ViewId,
view_class: &dyn ViewClass,
) -> &mut dyn ViewState {
self.states
.entry((store_id.clone(), view_id))
.or_insert_with(|| view_class.new_state())
.as_mut()
}
pub fn ensure_state_exists(
&mut self,
store_id: &StoreId,
view_id: ViewId,
view_class: &dyn ViewClass,
) {
self.states
.entry((store_id.clone(), view_id))
.or_insert_with(|| view_class.new_state());
}
pub fn reset_visualizer_reports(&mut self) {
self.visualizer_reports.clear();
}
pub fn add_visualizer_reports_from_output(
&mut self,
store_id: &StoreId,
view_id: ViewId,
system_output: &SystemExecutionOutput,
) {
let per_visualizer_reports = self
.visualizer_reports
.entry((store_id.clone(), view_id))
.or_default();
per_visualizer_reports.extend(system_output.visualizer_execution_output.iter().filter_map(
|(id, result)| VisualizerTypeReport::from_result(result).map(|error| (*id, error)),
));
}
pub fn per_visualizer_type_reports(
&self,
store_id: &StoreId,
view_id: ViewId,
) -> Option<&VisualizerViewReport> {
self.visualizer_reports.get(&(store_id.clone(), view_id))
}
pub fn set_view_reports(
&mut self,
store_id: &StoreId,
view_id: ViewId,
reports: Vec<ViewerDiagnostic>,
) {
let key = (store_id.clone(), view_id);
if reports.is_empty() {
self.view_reports.remove(&key);
} else {
self.view_reports.insert(key, reports);
}
}
pub fn view_reports(&self, store_id: &StoreId, view_id: ViewId) -> &[ViewerDiagnostic] {
self.view_reports
.get(&(store_id.clone(), view_id))
.map_or(&[], Vec::as_slice)
}
pub fn all_reports(
&self,
store_id: &StoreId,
view_id: ViewId,
) -> impl Iterator<Item = &ViewerDiagnostic> {
let visualizer_reports = self
.per_visualizer_type_reports(store_id, view_id)
.into_iter()
.flat_map(|reports| reports.values())
.flat_map(VisualizerTypeReport::diagnostics);
std::iter::chain(
self.view_reports(store_id, view_id).iter(),
visualizer_reports,
)
}
}