Skip to main content

runmat_runtime/builtins/plotting/
mod.rs

1//! Plotting builtins backed by the runmat-plot renderer.
2
3#[path = "core/common.rs"]
4pub(crate) mod common;
5#[path = "core/context.rs"]
6pub mod context;
7#[path = "core/engine.rs"]
8pub(crate) mod engine;
9#[path = "core/gpu_helpers.rs"]
10pub(crate) mod gpu_helpers;
11#[path = "core/perf.rs"]
12pub(crate) mod perf;
13#[path = "core/point.rs"]
14pub(crate) mod point;
15#[path = "core/properties.rs"]
16pub(crate) mod properties;
17#[path = "core/state.rs"]
18pub(crate) mod state;
19#[path = "core/style.rs"]
20pub(crate) mod style;
21#[path = "core/web.rs"]
22pub mod web;
23
24#[path = "type_resolvers.rs"]
25pub(crate) mod type_resolvers;
26
27#[path = "ops/addpoints.rs"]
28pub(crate) mod addpoints;
29#[path = "ops/ancestor.rs"]
30pub(crate) mod ancestor;
31#[path = "ops/animatedline.rs"]
32pub(crate) mod animatedline;
33#[path = "ops/area.rs"]
34pub(crate) mod area;
35#[path = "ops/axes.rs"]
36pub(crate) mod axes;
37#[path = "ops/axis_scale.rs"]
38pub(crate) mod axis_scale;
39#[path = "ops/axis_tick_angle.rs"]
40pub(crate) mod axis_tick_angle;
41#[path = "ops/axis_tick_format.rs"]
42pub(crate) mod axis_tick_format;
43#[path = "ops/axis_tick_labels.rs"]
44pub(crate) mod axis_tick_labels;
45#[path = "ops/axis_ticks.rs"]
46pub(crate) mod axis_ticks;
47#[path = "ops/bar.rs"]
48pub(crate) mod bar;
49#[path = "ops/caxis.rs"]
50pub(crate) mod caxis;
51#[path = "ops/clf.rs"]
52pub(crate) mod clf;
53#[path = "ops/clim.rs"]
54pub(crate) mod clim;
55#[path = "ops/close.rs"]
56pub(crate) mod close;
57#[path = "ops/cmds.rs"]
58pub(crate) mod cmds;
59#[path = "ops/colormap_arrays.rs"]
60pub(crate) mod colormap_arrays;
61#[path = "ops/colororder.rs"]
62pub(crate) mod colororder;
63#[path = "ops/contour.rs"]
64pub(crate) mod contour;
65#[path = "ops/contour3.rs"]
66pub(crate) mod contour3;
67#[path = "ops/contourf.rs"]
68pub(crate) mod contourf;
69#[path = "ops/copyobj.rs"]
70pub(crate) mod copyobj;
71#[path = "ops/daspect.rs"]
72pub(crate) mod daspect;
73#[path = "ops/data_tip_text_row.rs"]
74pub(crate) mod data_tip_text_row;
75#[path = "ops/datacursormode.rs"]
76pub(crate) mod datacursormode;
77#[path = "ops/drawnow.rs"]
78pub(crate) mod drawnow;
79#[path = "ops/errorbar.rs"]
80pub(crate) mod errorbar;
81#[path = "ops/fcontour.rs"]
82pub(crate) mod fcontour;
83#[path = "ops/figure.rs"]
84pub(crate) mod figure;
85#[path = "ops/figure_persistence.rs"]
86pub(crate) mod figure_persistence;
87#[path = "ops/fill.rs"]
88pub(crate) mod fill;
89#[path = "ops/fill3.rs"]
90pub(crate) mod fill3;
91#[path = "ops/findobj.rs"]
92pub(crate) mod findobj;
93#[path = "ops/fsurf.rs"]
94pub(crate) mod fsurf;
95#[path = "ops/gca.rs"]
96pub(crate) mod gca;
97#[path = "ops/gcf.rs"]
98pub(crate) mod gcf;
99#[path = "ops/get.rs"]
100pub(crate) mod get;
101#[path = "ops/gobjects.rs"]
102pub(crate) mod gobjects;
103#[path = "ops/groot.rs"]
104pub(crate) mod groot;
105#[path = "ops/heatmap.rs"]
106pub(crate) mod heatmap;
107#[path = "ops/hist.rs"]
108pub mod hist;
109#[path = "ops/histogram.rs"]
110pub(crate) mod histogram;
111#[path = "ops/histogram2.rs"]
112pub(crate) mod histogram2;
113#[path = "ops/hold.rs"]
114pub(crate) mod hold;
115#[path = "ops/image.rs"]
116pub(crate) mod image;
117#[path = "ops/imagesc.rs"]
118pub(crate) mod imagesc;
119#[path = "ops/imshow.rs"]
120pub(crate) mod imshow;
121#[path = "ops/isgraphics.rs"]
122pub(crate) mod isgraphics;
123#[path = "ops/ishandle.rs"]
124pub(crate) mod ishandle;
125#[path = "ops/legend.rs"]
126pub(crate) mod legend;
127#[path = "ops/line.rs"]
128pub(crate) mod line;
129#[path = "ops/linkaxes.rs"]
130pub(crate) mod linkaxes;
131#[path = "ops/loglog.rs"]
132pub(crate) mod loglog;
133#[path = "ops/mesh.rs"]
134pub(crate) mod mesh;
135#[path = "ops/meshc.rs"]
136pub(crate) mod meshc;
137#[path = "ops/common/mod.rs"]
138pub(crate) mod op_common;
139#[path = "ops/opengl.rs"]
140pub(crate) mod opengl;
141#[path = "ops/pan.rs"]
142pub(crate) mod pan;
143#[path = "ops/patch.rs"]
144pub(crate) mod patch;
145#[path = "ops/pie.rs"]
146pub(crate) mod pie;
147#[path = "ops/plot.rs"]
148pub(crate) mod plot;
149#[path = "ops/plot3.rs"]
150pub(crate) mod plot3;
151#[path = "ops/plotmatrix.rs"]
152pub(crate) mod plotmatrix;
153#[path = "ops/plotyy.rs"]
154pub(crate) mod plotyy;
155#[path = "ops/polarhistogram.rs"]
156pub(crate) mod polarhistogram;
157#[path = "ops/polarplot.rs"]
158pub(crate) mod polarplot;
159#[path = "ops/polarscatter.rs"]
160pub(crate) mod polarscatter;
161#[path = "ops/print.rs"]
162pub(crate) mod print;
163#[path = "ops/quiver.rs"]
164pub(crate) mod quiver;
165#[path = "ops/quiver3.rs"]
166pub(crate) mod quiver3;
167#[path = "ops/ribbon.rs"]
168pub(crate) mod ribbon;
169#[path = "ops/scatter.rs"]
170pub(crate) mod scatter;
171#[path = "ops/scatter3.rs"]
172pub(crate) mod scatter3;
173#[path = "ops/scatterplot.rs"]
174pub(crate) mod scatterplot;
175#[path = "ops/semilogx.rs"]
176pub(crate) mod semilogx;
177#[path = "ops/semilogy.rs"]
178pub(crate) mod semilogy;
179#[path = "ops/set.rs"]
180pub(crate) mod set;
181#[path = "ops/sgtitle.rs"]
182pub(crate) mod sgtitle;
183#[path = "ops/sphere.rs"]
184pub(crate) mod sphere;
185#[path = "ops/stackedplot.rs"]
186pub(crate) mod stackedplot;
187#[path = "ops/stairs.rs"]
188pub(crate) mod stairs;
189#[path = "ops/stem.rs"]
190pub(crate) mod stem;
191#[path = "ops/subplot.rs"]
192pub(crate) mod subplot;
193#[path = "ops/subtitle.rs"]
194pub(crate) mod subtitle;
195#[path = "ops/suptitle.rs"]
196pub(crate) mod suptitle;
197#[path = "ops/surf.rs"]
198pub(crate) mod surf;
199#[path = "ops/surfc.rs"]
200pub(crate) mod surfc;
201#[path = "ops/text.rs"]
202pub(crate) mod text;
203#[path = "ops/textscatter.rs"]
204pub(crate) mod textscatter;
205#[path = "ops/title.rs"]
206pub(crate) mod title;
207#[path = "ops/triplot.rs"]
208pub(crate) mod triplot;
209#[path = "ops/view.rs"]
210pub(crate) mod view;
211#[path = "ops/waitbar.rs"]
212pub(crate) mod waitbar;
213#[path = "ops/wordcloud.rs"]
214pub(crate) mod wordcloud;
215#[path = "ops/xlabel.rs"]
216pub(crate) mod xlabel;
217#[path = "ops/xlim.rs"]
218pub(crate) mod xlim;
219#[path = "ops/xline.rs"]
220pub(crate) mod xline;
221#[path = "ops/xscale.rs"]
222pub(crate) mod xscale;
223#[path = "ops/xtickangle.rs"]
224pub(crate) mod xtickangle;
225#[path = "ops/xtickformat.rs"]
226pub(crate) mod xtickformat;
227#[path = "ops/xticklabels.rs"]
228pub(crate) mod xticklabels;
229#[path = "ops/xticks.rs"]
230pub(crate) mod xticks;
231#[path = "ops/ylabel.rs"]
232pub(crate) mod ylabel;
233#[path = "ops/ylim.rs"]
234pub(crate) mod ylim;
235#[path = "ops/yline.rs"]
236pub(crate) mod yline;
237#[path = "ops/yscale.rs"]
238pub(crate) mod yscale;
239#[path = "ops/ytickangle.rs"]
240pub(crate) mod ytickangle;
241#[path = "ops/ytickformat.rs"]
242pub(crate) mod ytickformat;
243#[path = "ops/yticklabels.rs"]
244pub(crate) mod yticklabels;
245#[path = "ops/yticks.rs"]
246pub(crate) mod yticks;
247#[path = "ops/zlabel.rs"]
248pub(crate) mod zlabel;
249#[path = "ops/zlim.rs"]
250pub(crate) mod zlim;
251#[path = "ops/zoom.rs"]
252pub(crate) mod zoom;
253
254pub use perf::{
255    set_scatter_target_points, set_scene_export_budget_bytes, set_surface_vertex_budget,
256};
257pub use properties::resolve_plot_handle;
258pub use state::{
259    clear_figure, clone_figure, close_figure, configure_subplot, current_axes_state,
260    current_figure_handle, figure_handles, import_figure, install_figure_observer, link_axes,
261    new_figure_handle, record_recent_figure, reset_hold_state_for_run, reset_plot_state,
262    reset_recent_figures, select_axes_for_figure, select_figure, set_hold, take_recent_figures,
263    FigureAxesState, FigureError, FigureEventKind, FigureEventView, FigureHandle, HoldMode,
264    LinkAxesMode,
265};
266#[cfg(all(feature = "plot-core", target_arch = "wasm32"))]
267use std::cell::RefCell;
268use std::collections::HashMap;
269#[cfg(feature = "plot-core")]
270use std::sync::atomic::{AtomicU64, Ordering};
271use std::sync::{Mutex, OnceLock};
272use web::present_figure_on_surface as web_present_figure_on_surface;
273use web::present_geometry_scene_on_surface as web_present_geometry_scene_on_surface;
274pub use web::{
275    bind_surface_to_figure, clear_closed_figure_surfaces, detach_surface, fit_surface_extents,
276    get_surface_camera_state, install_surface, invalidate_surface_revisions,
277    pick_geometry_scene_region, present_surface, render_current_scene, reset_surface_camera,
278    resize_surface, set_geometry_scene_presentation, set_plot_theme_config,
279    set_surface_camera_state, web_renderer_ready, PlotCameraProjection, PlotCameraState,
280    PlotSurfaceCameraState,
281};
282
283#[doc(hidden)]
284pub use state::PlotTestLockGuard;
285
286#[doc(hidden)]
287pub fn lock_plot_test_context() -> PlotTestLockGuard {
288    state::lock_plot_test_registry()
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum RuntimePlottingMode {
293    Auto,
294    Interactive,
295    Static,
296}
297
298#[cfg(feature = "gui")]
299pub fn set_runtime_plotting_mode(mode: RuntimePlottingMode) {
300    let mapped = match mode {
301        RuntimePlottingMode::Auto => engine::native::RuntimePlottingMode::Auto,
302        RuntimePlottingMode::Interactive => engine::native::RuntimePlottingMode::Interactive,
303        RuntimePlottingMode::Static => engine::native::RuntimePlottingMode::Static,
304    };
305    engine::native::set_runtime_plotting_mode(mapped);
306}
307
308#[cfg(not(feature = "gui"))]
309pub fn set_runtime_plotting_mode(_mode: RuntimePlottingMode) {}
310
311#[cfg(all(target_arch = "wasm32", feature = "plot-web"))]
312pub use web::{handle_plot_surface_event, take_surface_host_actions, PlotSurfaceHostAction};
313
314pub(crate) fn plotting_error(builtin: &str, message: impl Into<String>) -> crate::RuntimeError {
315    crate::build_runtime_error(message)
316        .with_builtin(builtin)
317        .build()
318}
319
320pub(crate) fn plotting_error_with_source(
321    builtin: &str,
322    message: impl Into<String>,
323    source: impl std::error::Error + Send + Sync + 'static,
324) -> crate::RuntimeError {
325    crate::build_runtime_error(message)
326        .with_builtin(builtin)
327        .with_source(source)
328        .build()
329}
330
331#[cfg(feature = "plot-core")]
332pub async fn export_figure_scene(handle: FigureHandle) -> crate::BuiltinResult<Option<Vec<u8>>> {
333    export_figure_scene_with_policy(
334        handle,
335        runmat_plot::event::resolve_scene_export_policy(Some(perf::scene_export_budget_bytes())),
336    )
337    .await
338}
339
340#[cfg(feature = "plot-core")]
341pub async fn export_figure_scene_with_policy(
342    handle: FigureHandle,
343    policy: runmat_plot::event::SceneExportPolicy,
344) -> crate::BuiltinResult<Option<Vec<u8>>> {
345    let Some(figure) = clone_figure(handle) else {
346        return Ok(None);
347    };
348    let scene = runmat_plot::event::FigureScene::capture_for_export(&figure, policy)
349        .await
350        .map_err(|err| {
351            crate::replay_error_with_source(
352                crate::ReplayErrorKind::ExportRejected,
353                "invalid figure scene content",
354                err,
355            )
356        })?;
357    crate::replay::export_figure_scene_payload_with_limits(
358        &scene,
359        crate::replay::limits::ReplayLimits {
360            max_scene_payload_bytes: policy.max_scene_bytes,
361            ..crate::replay::limits::ReplayLimits::default()
362        },
363    )
364    .map(Some)
365}
366
367#[cfg(feature = "plot-core")]
368pub fn import_figure_scene(bytes: &[u8]) -> crate::BuiltinResult<Option<FigureHandle>> {
369    let scene = crate::replay::import_figure_scene_payload(bytes)?;
370    let figure = scene.into_figure().map_err(|err| {
371        crate::replay_error_with_source(
372            crate::ReplayErrorKind::ImportRejected,
373            "invalid figure scene content",
374            std::io::Error::new(std::io::ErrorKind::InvalidData, err),
375        )
376    })?;
377    let handle = import_figure(figure);
378    register_imported_figure(handle.as_u32());
379    Ok(Some(handle))
380}
381
382#[cfg(feature = "plot-core")]
383pub fn import_geometry_scene_payload(bytes: &[u8]) -> crate::BuiltinResult<Option<u32>> {
384    let scene = crate::replay::import_figure_scene_payload(bytes)?;
385    let hash = geometry_scene_payload_hash(bytes);
386    let scene_id = format!("geometry-scene-payload:{hash:016x}");
387    let scene = scene.into_geometry_scene(scene_id, hash).map_err(|err| {
388        crate::replay_error_with_source(
389            crate::ReplayErrorKind::ImportRejected,
390            "invalid geometry scene content",
391            std::io::Error::new(std::io::ErrorKind::InvalidData, err),
392        )
393    })?;
394    import_geometry_scene(scene).map(Some)
395}
396
397#[cfg(feature = "plot-core")]
398pub async fn import_figure_scene_async(bytes: &[u8]) -> crate::BuiltinResult<Option<FigureHandle>> {
399    let scene = crate::replay::import_figure_scene_payload_async(bytes).await?;
400    let figure = scene.into_figure().map_err(|err| {
401        crate::replay_error_with_source(
402            crate::ReplayErrorKind::ImportRejected,
403            "invalid figure scene content",
404            std::io::Error::new(std::io::ErrorKind::InvalidData, err),
405        )
406    })?;
407    let handle = import_figure(figure);
408    register_imported_figure(handle.as_u32());
409    Ok(Some(handle))
410}
411
412#[cfg(feature = "plot-core")]
413pub async fn import_figure_scene_from_path_async(
414    path: &str,
415) -> crate::BuiltinResult<Option<FigureHandle>> {
416    let bytes = runmat_filesystem::read_async(path).await.map_err(|err| {
417        crate::replay_error_with_source(
418            crate::ReplayErrorKind::ImportRejected,
419            format!("failed to read figure scene payload '{path}'"),
420            err,
421        )
422    })?;
423    import_figure_scene_async(&bytes).await
424}
425
426#[cfg(feature = "plot-core")]
427pub fn import_geometry_scene(scene: runmat_plot::GeometryScene) -> crate::BuiltinResult<u32> {
428    let handle = NEXT_GEOMETRY_SCENE_HANDLE.fetch_add(1, Ordering::Relaxed) as u32;
429    insert_geometry_scene(handle, scene)?;
430    register_imported_geometry_scene(handle);
431    Ok(handle)
432}
433
434#[cfg(feature = "plot-core")]
435pub fn clone_geometry_scene(handle: u32) -> Option<runmat_plot::GeometryScene> {
436    get_geometry_scene(handle)
437}
438
439#[cfg(feature = "plot-core")]
440pub fn append_geometry_scene_chunks(
441    handle: u32,
442    chunks: Vec<runmat_plot::GeometrySceneChunk>,
443    overlay: Option<runmat_plot::GeometrySceneOverlay>,
444) -> crate::BuiltinResult<()> {
445    with_geometry_scene_mut(handle, |scene| {
446        if !chunks.is_empty() {
447            scene.append_chunks(chunks);
448        }
449        if let Some(overlay) = overlay {
450            let merged = merge_geometry_scene_overlay(scene, overlay);
451            scene.set_overlay(merged);
452        }
453    })
454}
455
456#[cfg(feature = "plot-core")]
457pub fn close_geometry_scene(handle: u32) -> bool {
458    remove_geometry_scene(handle)
459}
460
461#[cfg(feature = "plot-core")]
462pub fn export_geometry_scene(handle: u32) -> crate::BuiltinResult<Option<Vec<u8>>> {
463    let Some(scene) = clone_geometry_scene(handle) else {
464        return Ok(None);
465    };
466    let scene = runmat_plot::event::FigureScene::from_geometry_scene(&scene);
467    crate::replay::export_figure_scene_payload(&scene).map(Some)
468}
469
470#[cfg(feature = "plot-core")]
471pub fn present_geometry_scene_on_surface(surface_id: u32, handle: u32) -> crate::BuiltinResult<()> {
472    let Some(scene) = clone_geometry_scene(handle) else {
473        return Err(crate::build_runtime_error(format!(
474            "geometry scene handle {handle} does not exist"
475        ))
476        .with_builtin("plot")
477        .build());
478    };
479    web_present_geometry_scene_on_surface(surface_id, handle, scene)?;
480    if take_imported_geometry_scene(handle) {
481        let _ = reset_surface_camera(surface_id);
482    }
483    Ok(())
484}
485
486pub fn present_figure_on_surface(surface_id: u32, handle: u32) -> crate::BuiltinResult<()> {
487    web_present_figure_on_surface(surface_id, handle)?;
488    if take_imported_figure(handle) {
489        let _ = reset_surface_camera(surface_id);
490    }
491    Ok(())
492}
493
494#[cfg(feature = "plot-core")]
495pub fn import_runtime_figure(figure: runmat_plot::plots::Figure) -> u32 {
496    let handle = state::import_figure(figure);
497    register_imported_figure(handle.as_u32());
498    handle.as_u32()
499}
500
501type ImportedFigureRegistry = Mutex<HashMap<u32, ()>>;
502#[cfg(all(feature = "plot-core", not(target_arch = "wasm32")))]
503type GeometrySceneRegistry = Mutex<HashMap<u32, runmat_plot::GeometryScene>>;
504#[cfg(feature = "plot-core")]
505type ImportedGeometrySceneRegistry = Mutex<HashMap<u32, ()>>;
506
507#[cfg(feature = "plot-core")]
508static NEXT_GEOMETRY_SCENE_HANDLE: AtomicU64 = AtomicU64::new(1);
509
510#[cfg(all(feature = "plot-core", target_arch = "wasm32"))]
511thread_local! {
512    static GEOMETRY_SCENE_REGISTRY: RefCell<HashMap<u32, runmat_plot::GeometryScene>> =
513        RefCell::new(HashMap::new());
514}
515
516#[cfg(all(feature = "plot-core", not(target_arch = "wasm32")))]
517fn geometry_scene_registry() -> &'static GeometrySceneRegistry {
518    static REGISTRY: OnceLock<GeometrySceneRegistry> = OnceLock::new();
519    REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
520}
521
522#[cfg(all(feature = "plot-core", not(target_arch = "wasm32")))]
523fn insert_geometry_scene(
524    handle: u32,
525    scene: runmat_plot::GeometryScene,
526) -> crate::BuiltinResult<()> {
527    let mut guard = geometry_scene_registry().lock().map_err(|_| {
528        crate::build_runtime_error("geometry scene registry lock poisoned")
529            .with_builtin("plot")
530            .build()
531    })?;
532    guard.insert(handle, scene);
533    Ok(())
534}
535
536#[cfg(all(feature = "plot-core", target_arch = "wasm32"))]
537fn insert_geometry_scene(
538    handle: u32,
539    scene: runmat_plot::GeometryScene,
540) -> crate::BuiltinResult<()> {
541    GEOMETRY_SCENE_REGISTRY.with(|registry| {
542        registry.borrow_mut().insert(handle, scene);
543    });
544    Ok(())
545}
546
547#[cfg(all(feature = "plot-core", not(target_arch = "wasm32")))]
548fn get_geometry_scene(handle: u32) -> Option<runmat_plot::GeometryScene> {
549    geometry_scene_registry().lock().ok()?.get(&handle).cloned()
550}
551
552#[cfg(all(feature = "plot-core", target_arch = "wasm32"))]
553fn get_geometry_scene(handle: u32) -> Option<runmat_plot::GeometryScene> {
554    GEOMETRY_SCENE_REGISTRY.with(|registry| registry.borrow().get(&handle).cloned())
555}
556
557#[cfg(all(feature = "plot-core", not(target_arch = "wasm32")))]
558fn with_geometry_scene_mut(
559    handle: u32,
560    update: impl FnOnce(&mut runmat_plot::GeometryScene),
561) -> crate::BuiltinResult<()> {
562    let mut guard = geometry_scene_registry().lock().map_err(|_| {
563        crate::build_runtime_error("geometry scene registry lock poisoned")
564            .with_builtin("plot")
565            .build()
566    })?;
567    let scene = guard.get_mut(&handle).ok_or_else(|| {
568        crate::build_runtime_error(format!("geometry scene handle {handle} does not exist"))
569            .with_builtin("plot")
570            .build()
571    })?;
572    update(scene);
573    Ok(())
574}
575
576#[cfg(all(feature = "plot-core", target_arch = "wasm32"))]
577fn with_geometry_scene_mut(
578    handle: u32,
579    update: impl FnOnce(&mut runmat_plot::GeometryScene),
580) -> crate::BuiltinResult<()> {
581    GEOMETRY_SCENE_REGISTRY.with(|registry| {
582        let mut registry = registry.borrow_mut();
583        let scene = registry.get_mut(&handle).ok_or_else(|| {
584            crate::build_runtime_error(format!("geometry scene handle {handle} does not exist"))
585                .with_builtin("plot")
586                .build()
587        })?;
588        update(scene);
589        Ok(())
590    })
591}
592
593#[cfg(all(feature = "plot-core", not(target_arch = "wasm32")))]
594fn remove_geometry_scene(handle: u32) -> bool {
595    geometry_scene_registry()
596        .lock()
597        .ok()
598        .and_then(|mut guard| guard.remove(&handle))
599        .is_some()
600}
601
602#[cfg(all(feature = "plot-core", target_arch = "wasm32"))]
603fn remove_geometry_scene(handle: u32) -> bool {
604    GEOMETRY_SCENE_REGISTRY.with(|registry| registry.borrow_mut().remove(&handle).is_some())
605}
606
607#[cfg(feature = "plot-core")]
608fn geometry_scene_payload_hash(bytes: &[u8]) -> u64 {
609    const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
610    const FNV_PRIME: u64 = 0x100000001b3;
611    let mut hash = FNV_OFFSET_BASIS;
612    for byte in bytes {
613        hash ^= u64::from(*byte);
614        hash = hash.wrapping_mul(FNV_PRIME);
615    }
616    hash
617}
618
619#[cfg(feature = "plot-core")]
620fn merge_geometry_scene_overlay(
621    scene: &runmat_plot::GeometryScene,
622    incoming: runmat_plot::GeometrySceneOverlay,
623) -> runmat_plot::GeometrySceneOverlay {
624    let Some(mut current) = scene.overlay.clone() else {
625        let mut overlay = incoming;
626        overlay.vertex_count = scene.vertex_count();
627        overlay.triangle_count = scene.triangle_count();
628        return overlay;
629    };
630
631    current.status = incoming.status;
632    current.quality_label = incoming.quality_label.or(current.quality_label);
633    current.format = incoming.format.or(current.format);
634    current.source_label = incoming.source_label.or(current.source_label);
635    current.allow_create_fea_study =
636        current.allow_create_fea_study || incoming.allow_create_fea_study;
637    current.byte_count = incoming.byte_count.or(current.byte_count);
638    current.mesh_count = current.mesh_count.max(incoming.mesh_count);
639    current.vertex_count = scene.vertex_count();
640    current.triangle_count = scene.triangle_count();
641    current.progress_percent = incoming.progress_percent;
642
643    if current.assembly_nodes.is_empty() {
644        current.assembly_nodes = incoming.assembly_nodes;
645    }
646
647    merge_region_summaries(&mut current.regions, incoming.regions);
648    current.region_count = current.regions.len();
649    current.mapped_region_count = current.region_count;
650    merge_warnings(&mut current.warnings, incoming.warnings);
651    current
652}
653
654#[cfg(feature = "plot-core")]
655fn merge_region_summaries(
656    current: &mut Vec<runmat_plot::GeometrySceneRegionSummary>,
657    incoming: Vec<runmat_plot::GeometrySceneRegionSummary>,
658) {
659    let mut positions = HashMap::<String, usize>::with_capacity(current.len() + incoming.len());
660    for (index, region) in current.iter().enumerate() {
661        positions.insert(region.region_id.clone(), index);
662    }
663    for region in incoming {
664        if let Some(index) = positions.get(&region.region_id).copied() {
665            current[index].triangle_count = current[index]
666                .triangle_count
667                .saturating_add(region.triangle_count);
668        } else {
669            positions.insert(region.region_id.clone(), current.len());
670            current.push(region);
671        }
672    }
673}
674
675#[cfg(feature = "plot-core")]
676fn merge_warnings(current: &mut Vec<String>, incoming: Vec<String>) {
677    for warning in incoming {
678        if !current.iter().any(|item| item == &warning) {
679            current.push(warning);
680        }
681    }
682}
683
684fn imported_figure_registry() -> &'static ImportedFigureRegistry {
685    static REGISTRY: OnceLock<ImportedFigureRegistry> = OnceLock::new();
686    REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
687}
688
689fn register_imported_figure(handle: u32) {
690    if let Ok(mut map) = imported_figure_registry().lock() {
691        map.insert(handle, ());
692    }
693}
694
695fn take_imported_figure(handle: u32) -> bool {
696    imported_figure_registry()
697        .lock()
698        .ok()
699        .and_then(|mut map| map.remove(&handle))
700        .is_some()
701}
702
703#[cfg(feature = "plot-core")]
704fn imported_geometry_scene_registry() -> &'static ImportedGeometrySceneRegistry {
705    static REGISTRY: OnceLock<ImportedGeometrySceneRegistry> = OnceLock::new();
706    REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
707}
708
709#[cfg(feature = "plot-core")]
710pub fn register_imported_geometry_scene(handle: u32) {
711    if let Ok(mut map) = imported_geometry_scene_registry().lock() {
712        map.insert(handle, ());
713    }
714}
715
716#[cfg(feature = "plot-core")]
717fn take_imported_geometry_scene(handle: u32) -> bool {
718    imported_geometry_scene_registry()
719        .lock()
720        .ok()
721        .and_then(|mut map| map.remove(&handle))
722        .is_some()
723}
724
725#[cfg(feature = "plot-core")]
726pub use engine::{
727    render_figure_png_bytes, render_figure_png_bytes_with_axes_cameras,
728    render_figure_png_bytes_with_camera, render_figure_rgba_bytes,
729    render_figure_rgba_bytes_with_axes_cameras, render_figure_rgba_bytes_with_camera,
730    render_figure_snapshot, render_figure_snapshot_with_camera_state,
731    render_geometry_scene_snapshot,
732};
733
734pub mod ops {
735    pub use super::hist;
736}
737
738#[cfg(test)]
739pub(crate) mod tests {
740    use super::state;
741
742    pub(crate) fn ensure_plot_test_env() {
743        state::disable_rendering_for_tests();
744    }
745
746    pub(crate) fn lock_plot_registry() -> state::PlotTestLockGuard {
747        state::lock_plot_test_registry()
748    }
749}