use super::*;
use crate::error::{BoxedTraverserError, GraphError, TraverserError, TraverserResult};
use crate::graph::{AnyGraph, ChangeSet, Transactional};
use crate::traversal::source::{ExecutedItems, ListItem};
#[doc(hidden)]
pub struct ScriptPlan<'a> {
pub(crate) source: &'a ScriptGraphTraversalSource,
pub(crate) mutating: bool,
}
#[doc(hidden)]
pub struct Terminal(pub(crate) TerminalKind);
pub(crate) enum TerminalKind {
ToList,
ListWithGraphs,
Profile(ProfileType),
Execute(crate::ExecOptions),
Visualize {
format: VisualizeFormat,
options: RenderOptions,
may_stop_early: bool,
},
Iterate,
ToGraph,
}
#[doc(hidden)]
pub struct TerminalOutput(pub(crate) OutputKind);
pub(crate) enum OutputKind {
List(TraverserResult<Vec<ElementProperty>>),
Items(TraverserResult<Vec<ListItem>>),
Metrics(TraverserResult<crate::TraversalMetrics>),
Executed(ExecutedItems<ListItem>),
Rendered(TraverserResult<RenderedOutput>),
Unit(TraverserResult<()>),
Graph(TraverserResult<Arc<Graph>>),
}
fn mismatch() -> BoxedTraverserError {
Box::new(TraverserError::NotImplemented {
feature: "a script terminal answered with another kind of result",
})
}
impl TerminalOutput {
pub(crate) fn into_list(self) -> TraverserResult<Vec<ElementProperty>> {
match self.0 {
OutputKind::List(result) => result,
_ => Err(mismatch()),
}
}
pub(crate) fn into_items(self) -> TraverserResult<Vec<ListItem>> {
match self.0 {
OutputKind::Items(result) => result,
_ => Err(mismatch()),
}
}
pub(crate) fn into_metrics(self) -> TraverserResult<crate::TraversalMetrics> {
match self.0 {
OutputKind::Metrics(result) => result,
_ => Err(mismatch()),
}
}
pub(crate) fn into_executed(self) -> ExecutedItems<ListItem> {
match self.0 {
OutputKind::Executed(run) => run,
_ => ExecutedItems {
items: Vec::new(),
error: Some(mismatch()),
profile: None,
mutated: false,
changes: Default::default(),
},
}
}
pub(crate) fn into_rendered(self) -> TraverserResult<RenderedOutput> {
match self.0 {
OutputKind::Rendered(result) => result,
_ => Err(mismatch()),
}
}
pub(crate) fn into_unit(self) -> TraverserResult<()> {
match self.0 {
OutputKind::Unit(result) => result,
_ => Err(mismatch()),
}
}
pub(crate) fn into_graph(self) -> TraverserResult<Arc<Graph>> {
match self.0 {
OutputKind::Graph(result) => result,
_ => Err(mismatch()),
}
}
}
pub(crate) fn run_plan_on<S: GraphStorage>(
graph: &Graph<S>,
plan: &ScriptPlan<'_>,
terminal: Terminal,
) -> TerminalOutput {
if plan.mutating {
let mut storage = graph.write();
run_terminal(plan.source.ast_convert_to_gts(&mut *storage), terminal)
} else {
let storage = graph.read();
run_terminal(plan.source.ast_convert_to_gts(&*storage), terminal)
}
}
fn run_terminal<'g, T: GraphStorage + 'g>(
gts: GraphTraversalSource<'g, T>,
terminal: Terminal,
) -> TerminalOutput {
TerminalOutput(match terminal.0 {
TerminalKind::ToList => OutputKind::List(gts.to_list()),
TerminalKind::ListWithGraphs => OutputKind::Items(gts.into_list_with_graphs()),
TerminalKind::Profile(flags) => OutputKind::Metrics(gts.profile_with(flags)),
TerminalKind::Execute(options) => {
OutputKind::Executed(gts.execute_items(&options, crate::traversal::source::list_item))
}
TerminalKind::Visualize {
format,
options,
may_stop_early,
} => OutputKind::Rendered(gts.visualize_bounded(format, &options, may_stop_early)),
TerminalKind::Iterate => OutputKind::Unit(gts.iterate()),
TerminalKind::ToGraph => OutputKind::Graph(gts.to_graph()),
})
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnitKind {
Atomic,
DryRun,
}
#[doc(hidden)]
pub struct UnitBody(pub(crate) Result<Dynamic, Box<rhai::EvalAltResult>>);
#[doc(hidden)]
pub struct UnitResult(pub(crate) Result<(Dynamic, Option<ChangeSet>), Box<rhai::EvalAltResult>>);
pub(crate) fn run_unit_on<S: GraphStorage + Default + Send + Sync + 'static>(
graph: &Graph<S>,
kind: UnitKind,
body: &mut dyn FnMut(&Arc<dyn AnyGraph>) -> UnitBody,
) -> UnitResult {
let mut shared = graph.write();
UnitResult(match kind {
UnitKind::Atomic => in_private_lock(graph, &mut *shared, |private, any| {
let mark = private.write().begin_unit();
let evaluated = crate::graph::storage::close_on_unwind(
&mut (),
|_| body(any).0,
|_| {
private.write().rollback_unit(
mark,
&crate::graph::RollbackReason::failed(&crate::graph::storage::UNWOUND),
)
},
);
match evaluated {
Ok(value) => match private.write().commit_unit(mark) {
Ok(()) => Ok((value, None)),
Err(err) => Err(script_error(Box::new(TraverserError::from(err)))),
},
Err(err) => {
private
.write()
.rollback_unit(mark, &crate::graph::RollbackReason::failed(&err));
Err(err)
}
}
}),
UnitKind::DryRun => shared
.dry_run(|unit| {
in_private_lock(graph, unit, |_, any| body(any).0).map_err(DryRunFailure)
})
.map(|(value, changes)| (value, Some(changes)))
.map_err(|failure| failure.0),
})
}
struct DryRunFailure(Box<rhai::EvalAltResult>);
impl From<GraphError> for DryRunFailure {
fn from(err: GraphError) -> Self {
DryRunFailure(script_error(Box::new(TraverserError::from(err))))
}
}
fn in_private_lock<S, R>(
template: &Graph<S>,
unit: &mut S,
body: impl FnOnce(&Arc<Graph<S>>, &Arc<dyn AnyGraph>) -> R,
) -> R
where
S: GraphStorage + Default + Send + Sync + 'static,
{
let private: Arc<Graph<S>> = Arc::new(template.new_like(std::mem::take(unit)));
let any: Arc<dyn AnyGraph> = private.clone();
let _restore = RestoreGraph {
unit,
private: &private,
};
body(&private, &any)
}
struct RestoreGraph<'a, S: GraphStorage> {
unit: &'a mut S,
private: &'a Arc<Graph<S>>,
}
impl<S: GraphStorage> Drop for RestoreGraph<'_, S> {
fn drop(&mut self) {
std::mem::swap(self.unit, &mut *self.private.write());
}
}
pub(crate) fn scratch_graph(mutating: bool) -> Arc<dyn AnyGraph> {
static EMPTY: std::sync::OnceLock<Arc<Graph>> = std::sync::OnceLock::new();
if mutating {
Arc::new(Graph::new(TraversalGraph::new()))
} else {
EMPTY
.get_or_init(|| Arc::new(Graph::new(TraversalGraph::new())))
.clone()
}
}
pub(crate) fn same_graph<A: ?Sized, B: ?Sized>(a: &Arc<A>, b: &Arc<B>) -> bool {
std::ptr::eq(Arc::as_ptr(a) as *const (), Arc::as_ptr(b) as *const ())
}