\{explanation}
\nPaused
\n\{scene.display_svg}
\n\n\n///|
/// Offline playback over a pure scene and a validated source-bound plan. The
/// earlier first-flow plan remains a compatible optional input.
pub struct AnimatedRenderer {
plan : @animation.MotionPlan?
playback : @animation.PlaybackPlan?
svg : @svg.SvgRenderer
limits : @animation.Limits
query_limits : @animation.Limits
sequence_view_id : String?
output_units : Int32
}
///|
pub fn AnimatedRenderer::new(
plan? : @animation.MotionPlan,
playback? : @animation.PlaybackPlan,
svg~ : @svg.SvgRenderer = @svg.SvgRenderer::new(),
limits~ : @animation.Limits = @animation.Limits::new(),
query_limits~ : @animation.Limits = @animation.Limits::new(),
sequence_view_id? : String,
output_units~ : Int32 = 33554432,
) -> AnimatedRenderer {
{ plan, playback, svg, limits, query_limits, sequence_view_id, output_units, }
}
///|
pub impl @render.Renderer for AnimatedRenderer with fn render(self, diagram) {
fn failed(code : String, message : String) -> @render.Rendered {
@render.Rendered::failed("text/html", [
@diagrams.Diagnostic::error(code, message),
])
}
if self.plan != None && self.playback != None {
return failed("animation/invalid-plan", "Supply one playback plan")
}
if !self.limits.valid() || !self.query_limits.valid() {
return failed(
"animation/plan-limit", "Animation and query limits must be valid",
)
}
let budget = HtmlBudget::{ limit: self.output_units, used: 0, failed: false, }
if self.output_units <= 0 ||
!budget.reserve(player_source.length() + player_style.length() + 10000) {
return failed("animation/render-limit", "HTML output limit is too small")
}
let view_id = self.sequence_view_id.unwrap_or_else(() => {
self.playback.map(p => p.view_id).unwrap_or("sequence")
})
let result = self.svg.scene(
diagram,
sequence_view_id=view_id,
limits=self.limits,
output_units=self.output_units,
)
guard result.scene is Some(scene) else {
return @render.Rendered::failed("text/html", result.diagnostics)
}
let planned = match self.playback {
Some(plan) => {
if !plan.matches(scene.catalog) {
return failed(
"animation/plan-mismatch", "Plan and scene name different source diagrams",
)
}
Some(plan)
}
None => None
}
let plan = match planned {
Some(plan) => plan
None => {
let planned = match self.plan {
Some(legacy) => {
if !(diagram is Bpmn(_)) || legacy.view_id != scene.catalog.view_id {
return failed(
"animation/plan-mismatch", "The legacy plan must name this BPMN view",
)
}
guard scene.catalog.flow(legacy.flow_id) is Some(occurrence) else {
return failed(
"animation/plan-mismatch", "Legacy plan names no visible connection",
)
}
let finite = @animation.PlaybackPlan::scenario(
scene.catalog,
Step(occurrence.key, legacy.duration_ms.to_int64()),
limits=self.limits,
)
guard finite.plan is Some(finite) else {
return @render.Rendered::failed("text/html", finite.diagnostics)
}
finite.repeating()
}
None =>
@animation.PlaybackPlan::illustrative(
scene.catalog,
limits=self.limits,
)
}
guard planned.plan is Some(plan) else {
return @render.Rendered::failed("text/html", planned.diagnostics)
}
plan
}
}
if !budget.reserve(scene.display_svg.length()) {
return failed(
"animation/render-limit", "Display SVG exceeds the HTML output limit",
)
}
let data = playback_data(
scene,
plan,
self.query_limits,
self.limits.steps,
budget,
)
if budget.failed {
return failed(
"animation/render-limit", "Embedded playback data exceeds the HTML output limit",
)
}
let encoded = safe_script_json(data)
let explanation = if plan.mode == Illustrative {
"Illustrative flow. Conditions, events and gateway synchronization are not evaluated."
} else {
"Explicit scenario. Selected connections illustrate the supplied order; no process execution is inferred."
}
let html = "\nDiagram flow illustration \n\n\{explanation}
\n\n\n\n\n\n\n\n\n \n\nPaused
\n\{scene.display_svg}\n\n\n \n"
if html.length() > self.output_units {
return failed("animation/render-limit", "HTML output limit exceeded")
}
@render.Rendered::ok(html, "text/html", diagnostics=result.diagnostics)
}
///|
pub extend AnimatedRenderer with @render.Renderer::{render}