///|
pub enum WorkflowMode {
Development
Validation
Release
} derive(Debug, Eq, ToJson, FromJson)
///|
pub enum ArtifactKind {
Atlas
Animation
Runtime
Diagnostics
} derive(Debug, Eq, ToJson, FromJson)
///|
pub struct WorkflowOptions {
name : String
mode : WorkflowMode
atlas_strategy : AtlasStrategy
max_width : Int
padding : Int
runtime : RuntimeTarget
include_diagnostics : Bool
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn WorkflowOptions::development(name : String) -> WorkflowOptions {
{
name,
mode: Development,
atlas_strategy: Rows,
max_width: 1024,
padding: 1,
runtime: Generic,
include_diagnostics: true,
}
}
///|
pub fn WorkflowOptions::validation(name : String) -> WorkflowOptions {
{
name,
mode: Validation,
atlas_strategy: CompactRows,
max_width: 2048,
padding: 1,
runtime: Generic,
include_diagnostics: true,
}
}
///|
pub fn WorkflowOptions::release(name : String) -> WorkflowOptions {
{
name,
mode: Release,
atlas_strategy: PowerOfTwo,
max_width: 4096,
padding: 2,
runtime: MoonBit,
include_diagnostics: false,
}
}
///|
pub fn WorkflowOptions::with_atlas(
self : WorkflowOptions,
strategy : AtlasStrategy,
max_width : Int,
padding : Int,
) -> WorkflowOptions {
{ ..self, atlas_strategy: strategy, max_width, padding }
}
///|
pub fn WorkflowOptions::with_runtime(
self : WorkflowOptions,
runtime : RuntimeTarget,
) -> WorkflowOptions {
{ ..self, runtime, }
}
///|
pub fn WorkflowOptions::with_diagnostics(
self : WorkflowOptions,
enabled : Bool,
) -> WorkflowOptions {
{ ..self, include_diagnostics: enabled }
}
///|
pub struct WorkflowArtifact {
name : String
kind : ArtifactKind
payload : String
bytes : Int
} derive(Debug, Eq, ToJson, FromJson)
///|
pub struct WorkflowReport {
name : String
mode : WorkflowMode
artifacts : Array[WorkflowArtifact]
errors : Int
warnings : Int
frame_count : Int
animation_count : Int
passed : Bool
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn WorkflowReport::artifact_named(
self : WorkflowReport,
name : String,
) -> WorkflowArtifact? {
match self.artifacts.search_by(fn(artifact) { artifact.name == name }) {
Some(index) => Some(self.artifacts[index])
None => None
}
}
///|
pub fn WorkflowReport::has_kind(
self : WorkflowReport,
kind : ArtifactKind,
) -> Bool {
for artifact in self.artifacts {
if artifact.kind == kind {
return true
}
}
false
}
///|
pub fn WorkflowReport::total_bytes(self : WorkflowReport) -> Int {
self.artifacts.fold(init=0, (total, artifact) => total + artifact.bytes)
}
///|
pub fn WorkflowReport::to_json_string(
self : WorkflowReport,
indent? : Int = 2,
) -> String {
self.to_json().stringify(indent~)
}
///|
fn workflow_artifact(
name : String,
kind : ArtifactKind,
payload : String,
) -> WorkflowArtifact {
{ name, kind, payload, bytes: payload.length() }
}
///|
fn workflow_options_checked(options : WorkflowOptions) -> Array[Diagnostic] {
let result : Array[Diagnostic] = []
if options.name == "" {
result.push({
severity: Error,
code: "empty_workflow_name",
message: "workflow name must not be empty",
})
}
if options.max_width <= 0 {
result.push({
severity: Error,
code: "invalid_workflow_width",
message: "workflow atlas width must be positive",
})
}
if options.padding < 0 {
result.push({
severity: Error,
code: "invalid_workflow_padding",
message: "workflow padding must not be negative",
})
}
result
}
///|
pub fn build_workflow(
sheet : SpriteSheet,
options : WorkflowOptions,
) -> WorkflowReport {
let option_diagnostics = workflow_options_checked(options)
let enriched = attach_collision_slices(sheet)
let diagnostics = validate_sheet(enriched) + option_diagnostics
let summary = summarize_diagnostics(diagnostics)
let atlas = pack_strategy(enriched, strategy=options.atlas_strategy, options={
max_width: options.max_width,
padding: options.padding,
})
let machine = build_machine(enriched)
let atlas_name = options.name + ".atlas"
let animation_name = options.name + ".animations"
let runtime_name = options.name + "." + runtime_extension(options.runtime)
let artifacts : Array[WorkflowArtifact] = [
workflow_artifact(atlas_name, Atlas, atlas.to_json_string(indent=2)),
workflow_artifact(
animation_name,
Animation,
machine.to_json_string(indent=2),
),
workflow_artifact(
runtime_name,
Runtime,
export_runtime_bundle(
{
sheet: enriched,
atlas,
machine,
nine_patches: collect_nine_patches(enriched),
diagnostics,
},
target=options.runtime,
),
),
]
if options.include_diagnostics {
artifacts.push(
workflow_artifact(
options.name + ".diagnostics",
Diagnostics,
diagnostics_to_json(diagnostics),
),
)
}
{
name: options.name,
mode: options.mode,
artifacts,
errors: summary.errors,
warnings: summary.warnings,
frame_count: enriched.frames.length(),
animation_count: machine.clips.length(),
passed: summary.errors == 0,
}
}
///|
fn runtime_extension(target : RuntimeTarget) -> String {
match target {
Generic => "json"
Godot => "tres"
Phaser => "json"
MoonBit => "mbt.json"
}
}
///|
pub fn validate_workflow(
sheet : SpriteSheet,
options : WorkflowOptions,
) -> WorkflowReport {
build_workflow(sheet, {
..options,
mode: Validation,
include_diagnostics: true,
})
}
///|
pub fn release_workflow(sheet : SpriteSheet, name : String) -> WorkflowReport {
build_workflow(sheet, WorkflowOptions::release(name))
}
///|
pub fn development_workflow(
sheet : SpriteSheet,
name : String,
) -> WorkflowReport {
build_workflow(sheet, WorkflowOptions::development(name))
}
///|
pub fn workflow_is_releasable(report : WorkflowReport) -> Bool {
report.passed &&
report.has_kind(Atlas) &&
report.has_kind(Animation) &&
report.has_kind(Runtime)
}
///|
pub fn workflow_status(report : WorkflowReport) -> String {
if report.passed {
"passed"
} else {
"failed"
}
}
///|
pub fn workflow_artifact_names(report : WorkflowReport) -> Array[String] {
report.artifacts.map(fn(artifact) { artifact.name })
}
///|
pub fn workflow_artifact_kinds(report : WorkflowReport) -> Array[ArtifactKind] {
report.artifacts.map(fn(artifact) { artifact.kind })
}
///|
pub fn workflow_diagnostics(report : WorkflowReport) -> DiagnosticSummary {
{ errors: report.errors, warnings: report.warnings }
}
///|
pub fn workflow_rebuild_is_deterministic(
sheet : SpriteSheet,
options : WorkflowOptions,
) -> Bool {
let left = build_workflow(sheet, options)
let right = build_workflow(sheet, options)
left.to_json_string(indent=0) == right.to_json_string(indent=0)
}
///|
pub fn workflow_target_options(
name : String,
target : RuntimeTarget,
) -> WorkflowOptions {
WorkflowOptions::release(name).with_runtime(target)
}
///|
pub fn workflow_for_editor(name : String) -> WorkflowOptions {
WorkflowOptions::development(name).with_runtime(Generic)
}
///|
pub fn workflow_for_ci(name : String) -> WorkflowOptions {
WorkflowOptions::validation(name).with_diagnostics(true)
}
///|
pub fn workflow_for_shipping(name : String) -> WorkflowOptions {
WorkflowOptions::release(name).with_diagnostics(false)
}