about summary refs log tree commit diff stats
path: root/lib/Fornax
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Fornax')
-rw-r--r--lib/Fornax/CLI.rakumod123
-rw-r--r--lib/Fornax/GenerateFrame.rakumod100
2 files changed, 123 insertions, 100 deletions
diff --git a/lib/Fornax/CLI.rakumod b/lib/Fornax/CLI.rakumod
index 8b18215..d066ebf 100644
--- a/lib/Fornax/CLI.rakumod
+++ b/lib/Fornax/CLI.rakumod
@@ -1,5 +1,4 @@
-use Cairo;
-use Fornax::Hex2RGB;
+use Fornax::GenerateFrame;
 
 subset File of Str where *.IO.f;
 
@@ -19,7 +18,7 @@ multi sub MAIN(
     Int() :$batch = 4, #= number of iterations to process at once (default: 4)
     Int() :fps($frame-rate) = 1, #= frame rate for video solution (default: 1)
     Bool :$skip-video, #= skip video solution
-    Bool :$verbose = True, #= verbosity (default: True)
+    Bool :$debug, #= debug logs
 ) is export {
     my IO() $output = "%s/fornax-%s".sprintf(
         '/tmp', ('a'...'z', 'A'...'Z', 0...9).roll(8).join
@@ -27,40 +26,14 @@ multi sub MAIN(
     mkdir $output;
     die "Output directory doesn't exist" unless $output.d;
 
-    put "[fornax] Output: '$output'" if $verbose;
+    put "[fornax] Output: '$output'";
 
     my Str @lines = $input.IO.lines;
     my Int() %meta{Str} = Metadata.parse(@lines.first).Hash
-                             or die "Cannot parse metadata";
-
-    # Cells as defined by fornax format.
-    constant $PATH = '.';
-    constant $BLOK = '#';
-    constant $DEST = '$';
-    constant $STRT = '^';
-    constant $VIS = '-';
-    constant $CUR = '@';
-    constant $CURPATH = '~';
+                                  or die "Cannot parse metadata";
 
     constant %CANVAS = :1920width, :1080height;
 
-    # Colors.
-    constant %C = (
-        bg-main => "#ffffff",
-
-        red-subtle-bg => "#f2b0a2",
-        blue-subtle-bg => "#b5d0ff",
-        cyan-subtle-bg => "#c0efff",
-        green-subtle-bg => "#aecf90",
-
-        fg-main => "#000000",
-
-        fg-special-cold => "#093060",
-        fg-special-warm => "#5d3026",
-        fg-special-mild => "#184034",
-        fg-special-calm => "#61284f",
-    ).map: {.key => hex2rgb(.value)};
-
     # Every cell must be square. Get the maximum width, height and use
     # that to decide which is to be used.
     my Int %cell = width => %CANVAS<width> div %meta<cols>,
@@ -79,7 +52,8 @@ multi sub MAIN(
         $side = %cell<height>;
     }
 
-    enum IterStatus <Walking Blocked Completed>;
+    my $render-start = now;
+    my Int $total-frames = @lines.elems - 1;
 
     my Promise @p;
     for @lines.skip.kv -> $idx, $iter is rw {
@@ -88,89 +62,38 @@ multi sub MAIN(
         if @p.elems == $batch {
             await @p;
             @p = [];
+
+            print "\r";
+            print "%s  Remaining: %.2fs  Elapsed: %.2fs %s".sprintf(
+                "[fornax $idx/$total-frames]",
+                ((now - $render-start) / $idx) * ($total-frames - $idx),
+                now - $render-start, "        ",
+            );
         }
 
         push @p, start {
-            my IterStatus $status;
-            given $iter.substr(0, 1) {
-                when '|' { $status = Completed }
-                when '!' { $status = Blocked }
-                default { $status = Walking }
-            };
-
-            # Remove marker.
-            $iter .= substr(1) if $status == Completed|Blocked;
-
-            put "[fornax] $idx $iter $status" if $verbose;
-
-            my @grid = $iter.comb.rotor: %meta<cols>;
-            warn "Invalid grid: $idx $iter $status" unless @grid.elems == %meta<rows>;
-
-            given Cairo::Image.create(
-                Cairo::FORMAT_ARGB32, %CANVAS<width>, %CANVAS<height>
-            ) {
-                given Cairo::Context.new($_) {
-                    # Paint the entire canvas white.
-                    .rgb: |%C<bg-main>;
-                    .rectangle(0, 0, %CANVAS<width>, %CANVAS<height>);
-                    .fill;
-
-                    for ^%meta<rows> -> $r {
-                        for ^%meta<cols> -> $c {
-                            my Int @target = %excess<width> div 2 + $c * $side,
-                                             %excess<height> div 2 + $r * $side,
-                                             $side, $side;
-
-                            .rectangle: |@target;
-
-                            given @grid[$r][$c] -> $cell {
-                                # According to the format, current
-                                # position may be prioritized over
-                                # Destination symbol so we colorize it
-                                # according to $status.
-                                when $cell eq $CUR {
-                                    .rgba: |%C<fg-special-cold>, 0.56;
-                                    .rgba: |%C<fg-special-mild>, 0.72 if $status == Completed;
-                                    .rgba: |%C<fg-special-warm>, 0.72 if $status == Blocked;
-                                }
-                                when $cell eq $CURPATH {
-                                    .rgba: |%C<blue-subtle-bg>, 0.84;
-                                    .rgba: |%C<green-subtle-bg>, 0.96 if $status == Completed;
-                                    .rgba: |%C<red-subtle-bg>, 0.96 if $status == Blocked;
-                                }
-                                when $cell eq $VIS {
-                                    .rgba: |%C<cyan-subtle-bg>, 0.72;
-                                }
-                                when $cell eq $BLOK { .rgba: |%C<fg-main>, 0.56 }
-                                when $cell eq $STRT|$DEST { .rgba: |%C<fg-special-mild>, 0.72 }
-                                default { .rgba: |%C<fg-main>, 0.08 }
-                            }
-                            .fill :preserve;
-
-                            .rgb: |%C<fg-main>;
-                            .stroke;
-                        }
-                    }
-                }
-                .write_png("%s/%08d.png".sprintf: $output, $idx);
-                .finish;
-            }
+            generate-frame(
+                :%CANVAS, :%excess, :$side, :%meta, :$iter, :$idx, :$debug,
+                :out("%s/%08d.png".sprintf: $output, $idx),
+            );
         }
     }
     # Wait for remaining jobs to finish.
     await @p;
 
-    put "[fornax] Generated images." if $verbose;
+    print "\r";
+    put "[fornax] Generated $total-frames frames in %.2fs. %s".sprintf(
+        now - $render-start, " " x 16,
+    );
 
     unless $skip-video {
-        put "[fornax] Creating a slideshow." if $verbose;
+        put "[fornax] Creating a slideshow.";
 
-        my Str $log-level = $verbose ?? "info" !! "error";
+        my Str $log-level = $debug ?? "info" !! "error";
         run «ffmpeg -loglevel "$log-level" -r "$frame-rate" -i "$output/\%08d.png"
                     -vf 'tpad=stop_mode=clone:stop_duration=4'
                     -vcodec libx264 -crf 28 -pix_fmt yuv420p "$output/solution.mp4"»;
     }
-    put "[fornax] Output: '$output'";
 }
 
 multi sub MAIN(
diff --git a/lib/Fornax/GenerateFrame.rakumod b/lib/Fornax/GenerateFrame.rakumod
new file mode 100644
index 0000000..e19d342
--- /dev/null
+++ b/lib/Fornax/GenerateFrame.rakumod
@@ -0,0 +1,100 @@
+use Cairo;
+use Fornax::Hex2RGB;
+
+# Cells as defined by fornax format.
+constant $PATH = '.';
+constant $BLOK = '#';
+constant $DEST = '$';
+constant $STRT = '^';
+constant $VIS = '-';
+constant $CUR = '@';
+constant $CURPATH = '~';
+
+# Colors.
+constant %C = (
+    bg-main => "#ffffff",
+
+    red-subtle-bg => "#f2b0a2",
+    blue-subtle-bg => "#b5d0ff",
+    cyan-subtle-bg => "#c0efff",
+    green-subtle-bg => "#aecf90",
+
+    fg-main => "#000000",
+
+    fg-special-cold => "#093060",
+    fg-special-warm => "#5d3026",
+    fg-special-mild => "#184034",
+    fg-special-calm => "#61284f",
+).map: {.key => hex2rgb(.value)};
+
+enum IterStatus <Walking Blocked Completed>;
+
+sub generate-frame(
+    :%CANVAS, :$out, :%excess, :$side, :%meta, :$iter is copy
+    , :$idx, :$debug,
+) is export {
+    my IterStatus $status;
+    given $iter.substr(0, 1) {
+        when '|' { $status = Completed }
+        when '!' { $status = Blocked }
+        default { $status = Walking }
+    };
+
+    # Remove marker.
+    $iter .= substr(1) if $status == Completed|Blocked;
+
+    put "\n[fornax] $idx $iter $status" if $debug;
+
+    my @grid = $iter.comb.rotor: %meta<cols>;
+    warn "Invalid grid: $idx $iter $status" unless @grid.elems == %meta<rows>;
+
+    given Cairo::Image.create(
+        Cairo::FORMAT_ARGB32, %CANVAS<width>, %CANVAS<height>
+    ) {
+        given Cairo::Context.new($_) {
+            # Paint the entire canvas white.
+            .rgb: |%C<bg-main>;
+            .rectangle(0, 0, %CANVAS<width>, %CANVAS<height>);
+            .fill;
+
+            # This seems to be slower than creating an intermediate
+            # variable and assigning from that. Difference is not much
+            # so we'll ignore it.
+            for ^%meta<rows> X ^%meta<cols>  -> ($r, $c) {
+                my Int @target = %excess<width> div 2 + $c * $side,
+                                 %excess<height> div 2 + $r * $side,
+                                 $side, $side;
+
+                .rectangle: |@target;
+
+                given @grid[$r][$c] -> $cell {
+                    # According to the format, current position may be
+                    # prioritized over Destination symbol so we
+                    # colorize it according to $status.
+                    when $cell eq $CUR {
+                        .rgba: |%C<fg-special-cold>, 0.56;
+                        .rgba: |%C<fg-special-mild>, 0.72 if $status == Completed;
+                        .rgba: |%C<fg-special-warm>, 0.72 if $status == Blocked;
+                    }
+                    when $cell eq $CURPATH {
+                        .rgba: |%C<blue-subtle-bg>, 0.84;
+                        .rgba: |%C<green-subtle-bg>, 0.96 if $status == Completed;
+                        .rgba: |%C<red-subtle-bg>, 0.96 if $status == Blocked;
+                    }
+                    when $cell eq $VIS {
+                        .rgba: |%C<cyan-subtle-bg>, 0.72;
+                    }
+                    when $cell eq $BLOK { .rgba: |%C<fg-main>, 0.56 }
+                    when $cell eq $STRT|$DEST { .rgba: |%C<fg-special-mild>, 0.72 }
+                    default { .rgba: |%C<fg-main>, 0.08 }
+                }
+                .fill :preserve;
+
+                .rgb: |%C<fg-main>;
+                .stroke;
+            }
+        }
+        .write_png($out);
+        .finish;
+    }
+}