From b52372f0d49336b16d6604014b2f66401b1ce017 Mon Sep 17 00:00:00 2001 From: Jacob Haip Date: Sun, 23 Jul 2023 09:24:16 -0400 Subject: add support for layered drawing and use to mask tags --- pi/pi.tcl | 36 ++++++++++++++++++++++++++---------- virtual-programs/mask-tags.folk | 16 ++++++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 virtual-programs/mask-tags.folk diff --git a/pi/pi.tcl b/pi/pi.tcl index 18c583b0..ad94ec11 100644 --- a/pi/pi.tcl +++ b/pi/pi.tcl @@ -5,6 +5,7 @@ thread::errorproc errorproc namespace eval Display { variable WIDTH variable HEIGHT + variable LAYER 0 regexp {mode "(\d+)x(\d+)"} [exec fbset] -> WIDTH HEIGHT variable displayThread [thread::create { @@ -17,38 +18,53 @@ namespace eval Display { }] puts "Display thread id: $displayThread" + proc drawOnTop {func args} { + set Display::LAYER 1 + uplevel [list $func {*}$args] + set Display::LAYER 0 + } + proc stroke {points width color} { - uplevel [list Wish display runs [list Display::stroke $points $width $color]] + uplevel [list Wish display runs [list Display::stroke $points $width $color] on layer $Display::LAYER] } proc circle {x y radius thickness color} { - uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color]] + uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color] on layer $Display::LAYER] } proc text args { - uplevel [list Wish display runs [list Display::text {*}$args]] + uplevel [list Wish display runs [list Display::text {*}$args] on layer $Display::LAYER] } proc fillTriangle args { - uplevel [list Wish display runs [list Display::fillTriangle {*}$args]] + uplevel [list Wish display runs [list Display::fillTriangle {*}$args] on layer $Display::LAYER] } proc fillQuad args { - uplevel [list Wish display runs [list Display::fillQuad {*}$args]] + uplevel [list Wish display runs [list Display::fillQuad {*}$args] on layer $Display::LAYER] } proc fillPolygon args { - uplevel [list Wish display runs [list Display::fillPolygon {*}$args]] + uplevel [list Wish display runs [list Display::fillPolygon {*}$args] on layer $Display::LAYER] } variable displayTime none proc commit {} { set displayList [list] - foreach match [Statements::findMatches {/someone/ wishes display runs /command/}] { - lappend displayList [dict get $match command] + foreach match [Statements::findMatches {/someone/ wishes display runs /command/ on layer /layer/}] { + lappend displayList [list [dict get $match layer] [dict get $match command]] + } + + proc lcomp {a b} { + set layerA [lindex $a 0] + set layerB [lindex $b 0] + if {$layerA == $layerB} { + expr {[lindex $a 1 0] == "Display::text"} + } else { + expr {$layerA - $layerB} + } } - proc lcomp {a b} {expr {[lindex $a 0] == "Display::text"}} incr ::displayCount thread::send -head -async $Display::displayThread [format { set newDisplayCount %d @@ -68,7 +84,7 @@ namespace eval Display { set Display::displayTime "$displayTime" }] } $::displayCount \ - [join [lsort -command lcomp $displayList] "\n"] \ + [join [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] "\n"] \ [thread::id]] } } diff --git a/virtual-programs/mask-tags.folk b/virtual-programs/mask-tags.folk new file mode 100644 index 00000000..4f0ae4b2 --- /dev/null +++ b/virtual-programs/mask-tags.folk @@ -0,0 +1,16 @@ +When tag /something/ has corners /corners/ { + set tagCorners [lmap p $corners {::cameraToProjector $p}] + + set vecBottom [sub [lindex $tagCorners 1] [lindex $tagCorners 0]] + set vecRight [sub [lindex $tagCorners 2] [lindex $tagCorners 1]] + + set offsets {{-0.5 -0.5} {0.5 -0.5} {0.5 0.5} {-0.5 0.5}} + set scales [matmul $offsets [list $vecBottom $vecRight]] + set corners [add $tagCorners $scales] + + set p0 [lindex $corners 0] + set p1 [lindex $corners 1] + set p2 [lindex $corners 2] + set p3 [lindex $corners 3] + Display::drawOnTop Display::fillQuad $p0 $p1 $p2 $p3 black +} \ No newline at end of file -- cgit v1.2.3 From c14013d995efb7a06bc73433eaf5d8450743297d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Cuervo?= Date: Tue, 25 Jul 2023 15:05:01 -0400 Subject: Default to drawing display commands on layer 0 --- pi/pi.tcl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pi/pi.tcl b/pi/pi.tcl index 9169650b..b599e67b 100644 --- a/pi/pi.tcl +++ b/pi/pi.tcl @@ -25,27 +25,27 @@ namespace eval Display { } proc stroke {points width color} { - uplevel [list Wish display runs [list Display::stroke $points $width $color] on layer $Display::LAYER] + uplevel [list Wish display runs [list Display::stroke $points $width $color]] } proc circle {x y radius thickness color} { - uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color] on layer $Display::LAYER] + uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color]] } proc text args { - uplevel [list Wish display runs [list Display::text {*}$args] on layer $Display::LAYER] + uplevel [list Wish display runs [list Display::text {*}$args]] } proc fillTriangle args { - uplevel [list Wish display runs [list Display::fillTriangle {*}$args] on layer $Display::LAYER] + uplevel [list Wish display runs [list Display::fillTriangle {*}$args]] } proc fillQuad args { - uplevel [list Wish display runs [list Display::fillQuad {*}$args] on layer $Display::LAYER] + uplevel [list Wish display runs [list Display::fillQuad {*}$args]] } proc fillPolygon args { - uplevel [list Wish display runs [list Display::fillPolygon {*}$args] on layer $Display::LAYER] + uplevel [list Wish display runs [list Display::fillPolygon {*}$args]] } variable displayTime none @@ -54,6 +54,9 @@ namespace eval Display { foreach match [Statements::findMatches {/someone/ wishes display runs /command/ on layer /layer/}] { lappend displayList [list [dict get $match layer] [dict get $match command]] } + foreach match [Statements::findMatches {/someone/ wishes display runs /command/}] { + lappend displayList [list 0 [dict get $match command]] + } proc lcomp {a b} { set layerA [lindex $a 0] -- cgit v1.2.3 From ec60a28bff4816e6cd15afd7df91260657c55484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Cuervo?= Date: Tue, 25 Jul 2023 15:11:35 -0400 Subject: Re-add layer default to drawing methods --- pi/pi.tcl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pi/pi.tcl b/pi/pi.tcl index b599e67b..e465971c 100644 --- a/pi/pi.tcl +++ b/pi/pi.tcl @@ -25,27 +25,27 @@ namespace eval Display { } proc stroke {points width color} { - uplevel [list Wish display runs [list Display::stroke $points $width $color]] + uplevel [list Wish display runs [list Display::stroke $points $width $color] on layer $Display::LAYER] } proc circle {x y radius thickness color} { - uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color]] + uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color] on layer $Display::LAYER] } proc text args { - uplevel [list Wish display runs [list Display::text {*}$args]] + uplevel [list Wish display runs [list Display::text {*}$args] on layer $Display::LAYER] } proc fillTriangle args { - uplevel [list Wish display runs [list Display::fillTriangle {*}$args]] + uplevel [list Wish display runs [list Display::fillTriangle {*}$args] on layer $Display::LAYER] } proc fillQuad args { - uplevel [list Wish display runs [list Display::fillQuad {*}$args]] + uplevel [list Wish display runs [list Display::fillQuad {*}$args] on layer $Display::LAYER] } proc fillPolygon args { - uplevel [list Wish display runs [list Display::fillPolygon {*}$args]] + uplevel [list Wish display runs [list Display::fillPolygon {*}$args] on layer $Display::LAYER] } variable displayTime none -- cgit v1.2.3 From 9bd7671fe4b7519c5b65baf81f434321eab695b5 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 10 Aug 2023 09:50:35 -0400 Subject: Fix labeling on laptop --- laptop.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laptop.tcl b/laptop.tcl index c58dbb1f..6a640460 100644 --- a/laptop.tcl +++ b/laptop.tcl @@ -47,7 +47,7 @@ namespace eval Display { uplevel [list Wish display runs [list .display create line {*}[join $points] -fill $color -width $width]] } - proc text {fb x y scale text {radians 0}} { + proc text {x y scale text {radians 0}} { uplevel [list Wish display runs [list .display create text $x $y -text $text -font "Helvetica [expr {$scale * 12}]" -fill white -anchor center -angle [expr {$radians/3.14159*180}]]] } -- cgit v1.2.3 From afcf5d7ce45fe301efb7f846c54eeacf77d1ae27 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 10 Aug 2023 16:05:49 -0400 Subject: New Interact wifi --- hosts.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts.tcl b/hosts.tcl index 7bc292e6..015be2f4 100644 --- a/hosts.tcl +++ b/hosts.tcl @@ -34,7 +34,7 @@ if {[info exists ::env(FOLK_SHARE_NODE)]} { set ::shareNode "folk-arc.local" } elseif {$wifi eq "The Windfish"} { set ::shareNode "folk-dpip.local" - } elseif {$wifi eq "Moxie"} { + } elseif {$wifi eq "interact residency"} { set ::shareNode "folk-interact.local" } else { # there's no default. -- cgit v1.2.3 From ffa09456af9674a4dd2645b2067e9c2c3e379df3 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 10 Aug 2023 21:21:43 -0400 Subject: Move display to virtual program Mostly to make it easier to debug the error spam, but it's nice that it's more consistent too --- calibrate.tcl | 1 + main.tcl | 4 -- pi/Display.tcl | 2 - pi/pi.tcl | 90 ------------------------------------------- virtual-programs/display.folk | 83 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 96 deletions(-) create mode 100644 virtual-programs/display.folk diff --git a/calibrate.tcl b/calibrate.tcl index cf2471f4..48cf0b8e 100644 --- a/calibrate.tcl +++ b/calibrate.tcl @@ -1,3 +1,4 @@ +source "lib/language.tcl" source "lib/c.tcl" exec sudo systemctl stop folk diff --git a/main.tcl b/main.tcl index d00deb1a..b4122a08 100644 --- a/main.tcl +++ b/main.tcl @@ -193,10 +193,6 @@ proc StepImpl {} { Evaluator::Evaluate } - if {[namespace exists Display]} { - Display::commit ;# TODO: this is weird, not right level - } - set shareStatements [clauseset create] set shareAllWishes [expr {[llength [Statements::findMatches [list /someone/ wishes $::thisProcess shares all wishes]]] > 0}] set shareAllClaims [expr {[llength [Statements::findMatches [list /someone/ wishes $::thisProcess shares all claims]]] > 0}] diff --git a/pi/Display.tcl b/pi/Display.tcl index 1ecc8e69..1751aa4a 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -1,5 +1,3 @@ -source "lib/language.tcl" -source "lib/c.tcl" source "pi/cUtils.tcl" namespace eval Display {} diff --git a/pi/pi.tcl b/pi/pi.tcl index e465971c..a3b73281 100644 --- a/pi/pi.tcl +++ b/pi/pi.tcl @@ -2,96 +2,6 @@ package require Thread proc errorproc {id errorInfo} {puts "Thread error in $id: $errorInfo"} thread::errorproc errorproc -namespace eval Display { - variable WIDTH - variable HEIGHT - variable LAYER 0 - regexp {mode "(\d+)x(\d+)"} [exec fbset] -> WIDTH HEIGHT - - variable displayThread [thread::create { - source pi/Display.tcl - Display::init - puts "Display tid: [getTid]" - - set ::displayCount 0 - thread::wait - }] - puts "Display thread id: $displayThread" - - proc drawOnTop {func args} { - set Display::LAYER 1 - uplevel [list $func {*}$args] - set Display::LAYER 0 - } - - proc stroke {points width color} { - uplevel [list Wish display runs [list Display::stroke $points $width $color] on layer $Display::LAYER] - } - - proc circle {x y radius thickness color} { - uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color] on layer $Display::LAYER] - } - - proc text args { - uplevel [list Wish display runs [list Display::text {*}$args] on layer $Display::LAYER] - } - - proc fillTriangle args { - uplevel [list Wish display runs [list Display::fillTriangle {*}$args] on layer $Display::LAYER] - } - - proc fillQuad args { - uplevel [list Wish display runs [list Display::fillQuad {*}$args] on layer $Display::LAYER] - } - - proc fillPolygon args { - uplevel [list Wish display runs [list Display::fillPolygon {*}$args] on layer $Display::LAYER] - } - - variable displayTime none - proc commit {} { - set displayList [list] - foreach match [Statements::findMatches {/someone/ wishes display runs /command/ on layer /layer/}] { - lappend displayList [list [dict get $match layer] [dict get $match command]] - } - foreach match [Statements::findMatches {/someone/ wishes display runs /command/}] { - lappend displayList [list 0 [dict get $match command]] - } - - proc lcomp {a b} { - set layerA [lindex $a 0] - set layerB [lindex $b 0] - if {$layerA == $layerB} { - expr {[lindex $a 1 0] == "Display::text"} - } else { - expr {$layerA - $layerB} - } - } - - incr ::displayCount - thread::send -head -async $Display::displayThread [format { - set newDisplayCount %d - if {$::displayCount > $newDisplayCount} { - # we've already displayed a newer frame - return - } else { - set ::displayCount $newDisplayCount - } - - # Draw the display list - set displayTime [time { - %s - commitThenClearStaging - }] - thread::send -async "%s" [subst { - set Display::displayTime "$displayTime" - }] - } $::displayCount \ - [join [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] "\n"] \ - [thread::id]] - } -} - try { set keyboardThread [thread::create [format { source "pi/Keyboard.tcl" diff --git a/virtual-programs/display.folk b/virtual-programs/display.folk new file mode 100644 index 00000000..ce7c0c2c --- /dev/null +++ b/virtual-programs/display.folk @@ -0,0 +1,83 @@ +if {$::isLaptop} return + +namespace eval Display { + variable WIDTH + variable HEIGHT + variable LAYER 0 + regexp {mode "(\d+)x(\d+)"} [exec fbset] -> WIDTH HEIGHT + + proc drawOnTop {func args} { + set Display::LAYER 1 + uplevel [list $func {*}$args] + set Display::LAYER 0 + } + + proc stroke {points width color} { + uplevel [list Wish display runs [list Display::stroke $points $width $color] on layer $Display::LAYER] + } + + proc circle {x y radius thickness color} { + uplevel [list Wish display runs [list Display::circle $x $y $radius $thickness $color] on layer $Display::LAYER] + } + + proc text args { + uplevel [list Wish display runs [list Display::text {*}$args] on layer $Display::LAYER] + } + + proc fillTriangle args { + uplevel [list Wish display runs [list Display::fillTriangle {*}$args] on layer $Display::LAYER] + } + + proc fillQuad args { + uplevel [list Wish display runs [list Display::fillQuad {*}$args] on layer $Display::LAYER] + } + + proc fillPolygon args { + uplevel [list Wish display runs [list Display::fillPolygon {*}$args] on layer $Display::LAYER] + } + + variable displayTime none +} + +On process { + source pi/Display.tcl + Display::init + puts "Display tid: [getTid]" + + # TODO: Clean this up. We retract these so that we don't bounce + # statements back to the main Folk process that it sends us. + Retract /anyone/ wishes $::thisProcess shares all wishes + Retract /anyone/ wishes $::thisProcess shares all claims + Wish $::thisProcess shares statements like \ + [list /someone/ wishes /process/ receives statements like /pattern/] + Wish $::thisProcess shares statements like \ + [list /someone/ claims $::thisProcess has pid /pid/] + Wish $::thisProcess receives statements like \ + [list /someone/ wishes display runs /command/ on layer /layer/] + Wish $::thisProcess receives statements like \ + [list /someone/ wishes display runs /command/] + + forever { + set displayList [list] + foreach match [Statements::findMatches {/someone/ wishes display runs /command/ on layer /layer/}] { + lappend displayList [list [dict get $match layer] [dict get $match command]] + } + foreach match [Statements::findMatches {/someone/ wishes display runs /command/}] { + lappend displayList [list 0 [dict get $match command]] + } + + proc lcomp {a b} { + set layerA [lindex $a 0] + set layerB [lindex $b 0] + if {$layerA == $layerB} { + expr {[lindex $a 1 0] == "Display::text"} + } else { + expr {$layerA - $layerB} + } + } + + set displayCommands [join [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] "\n"] + append displayCommands "\ncommitThenClearStaging" + set displayTime [time $displayCommands] + } +} -- cgit v1.2.3 From 6635ab4466548a2f9e370424f17cf68c1799605d Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 10 Aug 2023 21:25:17 -0400 Subject: Report display time --- virtual-programs/display.folk | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/virtual-programs/display.folk b/virtual-programs/display.folk index ce7c0c2c..a771b045 100644 --- a/virtual-programs/display.folk +++ b/virtual-programs/display.folk @@ -56,6 +56,8 @@ On process { [list /someone/ wishes display runs /command/ on layer /layer/] Wish $::thisProcess receives statements like \ [list /someone/ wishes display runs /command/] + Wish $::thisProcess shares statements like \ + [list /someone/ claims the display time is /displayTime/] forever { set displayList [list] @@ -79,5 +81,10 @@ On process { set displayCommands [join [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] "\n"] append displayCommands "\ncommitThenClearStaging" set displayTime [time $displayCommands] + Commit { Claim the display time is $displayTime } } } +# TODO: remove this compatibility hack +When the display time is /displayTime/ { + set ::Display::displayTime $displayTime +} -- cgit v1.2.3 From 1b887d8b53637e9c6a85a901e4e4ea3294bdddf2 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 11 Aug 2023 16:50:38 -0400 Subject: Add timestamps to camera frames --- virtual-programs/apriltags.folk | 61 ++++++++++++++++++++++++++++++++--------- virtual-programs/camera.folk | 11 ++++++-- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/virtual-programs/apriltags.folk b/virtual-programs/apriltags.folk index 01377b78..3e95ee64 100644 --- a/virtual-programs/apriltags.folk +++ b/virtual-programs/apriltags.folk @@ -9,29 +9,64 @@ On process { Retract /anyone/ wishes $::thisProcess shares all wishes Retract /anyone/ wishes $::thisProcess shares all claims Wish $::thisProcess receives statements like \ - [list /someone/ claims the camera frame is /grayFrame/] + [list /someone/ claims the camera frame is /grayFrame/ at /timestamp/] Wish $::thisProcess shares statements like \ [list /someone/ wishes /process/ receives statements like /pattern/] Wish $::thisProcess shares statements like \ - [list /someone/ claims tag /tag/ has center /center/ size /size/] - Wish $::thisProcess shares statements like \ - [list /someone/ claims tag /tag/ has corners /corners/] - Wish $::thisProcess shares statements like \ - [list /someone/ claims the AprilTag time is /aprilTime/] + [list /someone/ claims /process/ detects tags /tags/ at /timestamp/ in time /aprilTime/] Wish $::thisProcess shares statements like \ [list /someone/ claims $::thisProcess has pid /pid/] - When the camera frame is /grayFrame/ { + When the camera frame is /grayFrame/ at /timestamp/ { set aprilTime [time { set tags [AprilTags::detect $grayFrame] }] Commit { - Claim the AprilTag time is $aprilTime - - foreach tag $tags { - Claim tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size] - Claim tag [dict get $tag id] has corners [dict get $tag corners] - } + Claim $::thisProcess detects tags $tags at $timestamp in time $aprilTime } } } + +When /someone/ detects tags /tags/ at /timestamp/ in time /aprilTime/ { + Claim the AprilTag time is $aprilTime + foreach tag $tags { + Claim tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size] + Claim tag [dict get $tag id] has corners [dict get $tag corners] + } +} + +# On process { +# source pi/AprilTags.tcl +# AprilTags::init + +# # TODO: Clean this up. We retract these so that we don't bounce +# # statements back to the main Folk process that it sends us. +# Retract /anyone/ wishes $::thisProcess shares all wishes +# Retract /anyone/ wishes $::thisProcess shares all claims +# Wish $::thisProcess receives statements like \ +# [list /someone/ claims the camera frame is /grayFrame/] +# Wish $::thisProcess shares statements like \ +# [list /someone/ wishes /process/ receives statements like /pattern/] +# Wish $::thisProcess shares statements like \ +# [list /someone/ claims tag /tag/ has center /center/ size /size/] +# Wish $::thisProcess shares statements like \ +# [list /someone/ claims tag /tag/ has corners /corners/] +# Wish $::thisProcess shares statements like \ +# [list /someone/ claims the AprilTag time is /aprilTime/] +# Wish $::thisProcess shares statements like \ +# [list /someone/ claims $::thisProcess has pid /pid/] + +# When the camera frame is /grayFrame/ { +# set aprilTime [time { +# set tags [AprilTags::detect $grayFrame] +# }] +# Commit { +# Claim the AprilTag time is $aprilTime + +# foreach tag $tags { +# Claim tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size] +# Claim tag [dict get $tag id] has corners [dict get $tag corners] +# } +# } +# } +# } diff --git a/virtual-programs/camera.folk b/virtual-programs/camera.folk index d9dab784..4f59a576 100644 --- a/virtual-programs/camera.folk +++ b/virtual-programs/camera.folk @@ -18,15 +18,20 @@ On process { source pi/Camera.tcl Camera::init $width $height - puts "Camera tid: [getTid]" - + puts "Camera tid: [getTid] booting at [clock milliseconds]" + forever { set cameraTime [time { set grayFrame [Camera::grayFrame] }] Commit { Claim the camera time is $cameraTime - Claim the camera frame is $grayFrame + Claim the camera frame is $grayFrame at [clock milliseconds] } } } + +# For backward compatibility. +When the camera frame is /grayFrame/ at /timestamp/ { + Claim the camera frame is $grayFrame +} -- cgit v1.2.3 From 55f9cd4cc30e4fe157419315945d083184ecb871 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 11 Aug 2023 19:47:04 -0400 Subject: Measure display fps --- lib/language.tcl | 2 ++ virtual-programs/display.folk | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/language.tcl b/lib/language.tcl index b265b5a6..2b9fbc01 100644 --- a/lib/language.tcl +++ b/lib/language.tcl @@ -70,6 +70,8 @@ proc assert condition { } } +proc baretime body { string map {" microseconds per iteration" ""} [uplevel [list time $body]] } + # forever { ... } is sort of like while true { ... }, but it yields to # the event loop after each iteration. proc forever {body} { diff --git a/virtual-programs/display.folk b/virtual-programs/display.folk index a771b045..b1d16be7 100644 --- a/virtual-programs/display.folk +++ b/virtual-programs/display.folk @@ -59,6 +59,7 @@ On process { Wish $::thisProcess shares statements like \ [list /someone/ claims the display time is /displayTime/] + set ::frames [list] forever { set displayList [list] foreach match [Statements::findMatches {/someone/ wishes display runs /command/ on layer /layer/}] { @@ -78,10 +79,20 @@ On process { } } - set displayCommands [join [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] "\n"] - append displayCommands "\ncommitThenClearStaging" - set displayTime [time $displayCommands] - Commit { Claim the display time is $displayTime } + set displayCommands [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] + lappend displayCommands "commitThenClearStaging" + set displayTime [baretime [list foreach command $displayCommands { {*}$command }]] + + set inLastSecond 0 + set now [clock milliseconds] + lappend frames $now + foreach frame $frames { + if {$frame > $now - 1000} { + incr inLastSecond + } + } + set frames [lreplace $frames 0 end-$inLastSecond] + Commit { Claim the display time is "$displayTime us ($inLastSecond fps)" } } } # TODO: remove this compatibility hack -- cgit v1.2.3 From 7748c404173eaea6851920a54e808b2b9bcd01d5 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 11 Aug 2023 19:47:19 -0400 Subject: Try to retry drm master. Doesn't really help --- pi/Display.tcl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pi/Display.tcl b/pi/Display.tcl index 1751aa4a..4333a56d 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -128,10 +128,11 @@ dc proc setupGpu {} void { fprintf(stderr, "Display: cannot open '%s': %m\n", card); exit(1); } - if (drmSetMaster(gpuFd) != 0) { + while (drmSetMaster(gpuFd) != 0) { fprintf(stderr, "Display: cannot become DRM master on '%s': %m\n", card); - exit(1); + fprintf(stderr, "Display: waiting 1 s...\n"); sleep(1); } + fprintf(stderr, "Display: successfully became DRM master on '%s'\n", card); uint64_t hasDumb; if (drmGetCap(gpuFd, DRM_CAP_DUMB_BUFFER, &hasDumb) < 0 || !hasDumb) { fprintf(stderr, "Display: drm device '%s' does not support dumb buffers\n", card); -- cgit v1.2.3 From f6a84c325f0be85a69424b9f8659bffedde7adc3 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sat, 12 Aug 2023 17:30:44 -0400 Subject: 0:0 bug test that actually is repeatable --- test/stale.tcl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/stale.tcl diff --git a/test/stale.tcl b/test/stale.tcl new file mode 100644 index 00000000..32ca6860 --- /dev/null +++ b/test/stale.tcl @@ -0,0 +1,14 @@ +Assert when we are running {{} { + When the iteration count is /k/ { + Commit { Claim the counted iteration count is $k } + } +}} +Assert we are running +Step + +for {set i 0} {$i < 10000} {incr i} { + Commit [list Claim the iteration count is $i] + Step +} + +exec dot -Tpdf >stale.pdf <<[Statements::dot] -- cgit v1.2.3 From 3047b5b0675db8c1119750341b81e7519e5f8808 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sat, 12 Aug 2023 17:31:32 -0400 Subject: test/stale: Simplify --- test/stale.tcl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/stale.tcl b/test/stale.tcl index 32ca6860..189d6d9d 100644 --- a/test/stale.tcl +++ b/test/stale.tcl @@ -1,12 +1,4 @@ -Assert when we are running {{} { - When the iteration count is /k/ { - Commit { Claim the counted iteration count is $k } - } -}} -Assert we are running -Step - -for {set i 0} {$i < 10000} {incr i} { +for {set i 0} {$i < 30000} {incr i} { Commit [list Claim the iteration count is $i] Step } -- cgit v1.2.3 From 6e5d650a7a11a6fa34661e578be64298ecd601a8 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sat, 12 Aug 2023 17:35:40 -0400 Subject: test/stale: Use assert --- test/stale.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/stale.tcl b/test/stale.tcl index 189d6d9d..7b2fcd70 100644 --- a/test/stale.tcl +++ b/test/stale.tcl @@ -3,4 +3,4 @@ for {set i 0} {$i < 30000} {incr i} { Step } -exec dot -Tpdf >stale.pdf <<[Statements::dot] +assert {[llength [Statements::findMatches [list /someone/ claims the iteration count is /i/]]] == 1} -- cgit v1.2.3 From 207af8b48d7e17e540fcaa65dc01242b34ea0e63 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sat, 12 Aug 2023 18:09:49 -0400 Subject: Share w/o claim to try to reduce resharing. May not be needed --- main.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tcl b/main.tcl index b4122a08..27bae456 100644 --- a/main.tcl +++ b/main.tcl @@ -234,7 +234,7 @@ proc StepImpl {} { run [list apply {{process receivedStatements} { upvar chan chan Commit $chan statements { - Claim $process is sharing statements $receivedStatements + Say $process is sharing statements $receivedStatements } }} $::thisProcess [clauseset clauses $shareStatements]] -- cgit v1.2.3 From d31bb78a7d6c2b49aaaf280266412465915eb06b Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sat, 12 Aug 2023 18:10:19 -0400 Subject: Don't use value == 0 in trie, so 0:0 can actually match Keep a separate hasValue flag instead. Fixes #63 (previously, you could never remove statement 0:0 because you could never get that as a trie query result because value == 0 was invalid) --- lib/trie.tcl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/trie.tcl b/lib/trie.tcl index 467631b7..dc89dc3d 100644 --- a/lib/trie.tcl +++ b/lib/trie.tcl @@ -33,8 +33,8 @@ namespace eval ctrie { // We generally store a pointer (for example, to a // reaction thunk) or a generational handle (for example, - // for a statement) in this 64-bit value slot. Only used - // in leaf nodes of the trie. + // for a statement) in this 64-bit value slot. + bool hasValue; uint64_t value; size_t nbranches; @@ -47,6 +47,7 @@ namespace eval ctrie { trie_t* ret = (trie_t *) ckalloc(size); memset(ret, 0, size); *ret = (trie_t) { .key = NULL, + .hasValue = false, .value = 0, .nbranches = 10 }; @@ -83,6 +84,7 @@ namespace eval ctrie { $cc proc addImpl {trie_t** trie int wordc Tcl_Obj** wordv uint64_t value} void { if (wordc == 0) { (*trie)->value = value; + (*trie)->hasValue = true; return; } @@ -116,6 +118,7 @@ namespace eval ctrie { branch->key = word; Tcl_IncrRefCount(branch->key); branch->value = 0; + branch->hasValue = false; branch->nbranches = 10; (*trie)->branches[j] = branch; @@ -178,7 +181,7 @@ namespace eval ctrie { uint64_t* results int* resultsidx size_t maxresults trie_t* trie int wordc Tcl_Obj** wordv} void { if (wordc == 0) { - if (trie->value != 0) { + if (trie->hasValue) { if (*resultsidx < maxresults) { results[(*resultsidx)++] = trie->value; } -- cgit v1.2.3 From 13dac3dd6b3ddbd926bf240fdf9061ae7d2a4758 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sun, 13 Aug 2023 08:34:21 -0400 Subject: Add clock time for animation --- virtual-programs/time.folk | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 virtual-programs/time.folk diff --git a/virtual-programs/time.folk b/virtual-programs/time.folk new file mode 100644 index 00000000..8ce4c028 --- /dev/null +++ b/virtual-programs/time.folk @@ -0,0 +1,3 @@ +When $::thisProcess has step count /t/ { + Claim $::thisProcess has clock time [clock milliseconds] +} -- cgit v1.2.3 From bc1fc7ecb1718abb556408db88e53743df754bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Cuervo?= Date: Sun, 13 Aug 2023 09:36:36 -0400 Subject: Update global node time to seconds --- virtual-programs/time.folk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtual-programs/time.folk b/virtual-programs/time.folk index 8ce4c028..961b1863 100644 --- a/virtual-programs/time.folk +++ b/virtual-programs/time.folk @@ -1,3 +1,3 @@ When $::thisProcess has step count /t/ { - Claim $::thisProcess has clock time [clock milliseconds] + Claim the clock time is [/ [clock milliseconds] 1000.0] } -- cgit v1.2.3 From 19d00e6c3ec1e1b1847f939722c8517d2946e2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Cuervo?= Date: Sun, 13 Aug 2023 09:45:31 -0400 Subject: Add Animation (snippet) to lang reference --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 65dd409d..6ce006fb 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,26 @@ retrigger, and so on. in its body will run again unless the boop goes away and an entirely new boop appears. +### Animation + +#### Getting time + +Get the global clock time with: + +``` +When the clock time is /t/ { + Wish $this is labelled $t +} +``` + +Use it in an animation: + +``` +When the clock time is /t/ { + Wish $this draws a circle offset [list [expr {sin($t) * 50}] 0] +} +``` + ### You usually won't need these #### When when -- cgit v1.2.3 From 1484ed78971e1c6bdc0fa7995ef659bab0df027c Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sun, 13 Aug 2023 12:06:47 -0400 Subject: Region helper --- virtual-programs/regions.folk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/virtual-programs/regions.folk b/virtual-programs/regions.folk index 5de19b98..945fcd20 100644 --- a/virtual-programs/regions.folk +++ b/virtual-programs/regions.folk @@ -1,3 +1,8 @@ When when the distance between /p1/ and /p2/ is /distanceVar/ /body/ with environment /e/ & /p1/ has region /r1/ & /p2/ has region /r2/ { Claim the distance between $p1 and $p2 is [region distance $r1 $r2] } + +When /someone/ wishes region /r/ is /verbed/ /x/ { + Claim $r has region $r + Wish $r is $verbed $x +} -- cgit v1.2.3 From 7d462c3bd10b29da1063c338ae66ab253a1e71d8 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sun, 13 Aug 2023 19:26:00 -0400 Subject: display: Show render/commit split --- virtual-programs/display.folk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/virtual-programs/display.folk b/virtual-programs/display.folk index b1d16be7..d5623d10 100644 --- a/virtual-programs/display.folk +++ b/virtual-programs/display.folk @@ -80,8 +80,8 @@ On process { } set displayCommands [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] - lappend displayCommands "commitThenClearStaging" - set displayTime [baretime [list foreach command $displayCommands { {*}$command }]] + set renderTime [baretime [list foreach command $displayCommands { {*}$command }]] + set commitTime [baretime commitThenClearStaging] set inLastSecond 0 set now [clock milliseconds] @@ -92,7 +92,7 @@ On process { } } set frames [lreplace $frames 0 end-$inLastSecond] - Commit { Claim the display time is "$displayTime us ($inLastSecond fps)" } + Commit { Claim the display time is "render $renderTime us + commit $commitTime us ($inLastSecond fps)" } } } # TODO: remove this compatibility hack -- cgit v1.2.3 From 3ddc9e924e7e747f3d75501eaa8cd731d67f5e7c Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sun, 13 Aug 2023 23:07:57 -0400 Subject: More small monitoring improvements. Remote flamegraph tid --- Makefile | 4 +++- main.tcl | 4 ++-- pi/Display.tcl | 1 + virtual-programs/display.folk | 9 +++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 9c5c1528..6f5fce0b 100644 --- a/Makefile +++ b/Makefile @@ -38,8 +38,10 @@ flamegraph: ~/FlameGraph/stackcollapse-perf.pl out.perf > out.folded ~/FlameGraph/flamegraph.pl out.folded > out.svg +# You can use the Web server to check the pid of display.folk, +# apriltags.folk, camera.folk, etc. remote-flamegraph: - ssh -t folk@$(FOLK_SHARE_NODE) -- make -C /home/folk/folk flamegraph + ssh -t folk@$(FOLK_SHARE_NODE) -- make -C /home/folk/folk flamegraph $(if $(REMOTE_FLAMEGRAPH_TID),FLAMEGRAPH_TID=$(REMOTE_FLAMEGRAPH_TID),) scp folk@$(FOLK_SHARE_NODE):~/folk/out.svg . scp folk@$(FOLK_SHARE_NODE):~/folk/out.perf . diff --git a/main.tcl b/main.tcl index 27bae456..70a5034d 100644 --- a/main.tcl +++ b/main.tcl @@ -170,7 +170,7 @@ proc Commit {args} { } set ::stepCount 0 -set ::stepTime "none" +set ::stepTime -1 source "lib/peer.tcl" proc StepImpl {} { incr ::stepCount @@ -246,7 +246,7 @@ proc StepImpl {} { } proc Step {} { if {[dict size $::toCommit] > 0 || ![Evaluator::LogIsEmpty]} { - set ::stepTime [time StepImpl] + set ::stepTime [baretime StepImpl] } } diff --git a/pi/Display.tcl b/pi/Display.tcl index 4333a56d..2c7c50ef 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -251,6 +251,7 @@ dc proc commitThenClearStaging {} void { exit(1); } staging = fbs[!currentFbIndex].mem; + // This memset takes ~2ms on 1080p on a Pi 4. memset(staging, 0, fbwidth * fbheight * sizeof(pixel_t)); } diff --git a/virtual-programs/display.folk b/virtual-programs/display.folk index d5623d10..5e16797f 100644 --- a/virtual-programs/display.folk +++ b/virtual-programs/display.folk @@ -80,19 +80,20 @@ On process { } set displayCommands [lmap sublist [lsort -command lcomp $displayList] {lindex $sublist 1}] + set renderTime [baretime [list foreach command $displayCommands { {*}$command }]] set commitTime [baretime commitThenClearStaging] - set inLastSecond 0 + set framesInLastSecond 0 set now [clock milliseconds] lappend frames $now foreach frame $frames { if {$frame > $now - 1000} { - incr inLastSecond + incr framesInLastSecond } } - set frames [lreplace $frames 0 end-$inLastSecond] - Commit { Claim the display time is "render $renderTime us + commit $commitTime us ($inLastSecond fps)" } + set frames [lreplace $frames 0 end-$framesInLastSecond] + Commit { Claim the display time is "render $renderTime us + commit $commitTime us ($framesInLastSecond fps)" } } } # TODO: remove this compatibility hack -- cgit v1.2.3 From 11cdb9051327c873b334284ad8409be64fc6b3e7 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sun, 13 Aug 2023 23:08:28 -0400 Subject: WIP: Start on incremental detector. Fix stride in AprilTags --- pi/AprilTags.tcl | 2 +- virtual-programs/apriltags.folk | 107 ++++++++++++++++++++++++++++------------ 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/pi/AprilTags.tcl b/pi/AprilTags.tcl index a489a1f4..92e9cb71 100644 --- a/pi/AprilTags.tcl +++ b/pi/AprilTags.tcl @@ -22,7 +22,7 @@ namespace eval AprilTags { apc proc detect {image_t gray} Tcl_Obj* { assert(gray.components == 1); - image_u8_t im = (image_u8_t) { .width = gray.width, .height = gray.height, .stride = gray.width, .buf = gray.data }; + image_u8_t im = (image_u8_t) { .width = gray.width, .height = gray.height, .stride = gray.bytesPerRow, .buf = gray.data }; zarray_t *detections = apriltag_detector_detect(td, &im); int detectionCount = zarray_size(detections); diff --git a/virtual-programs/apriltags.folk b/virtual-programs/apriltags.folk index 3e95ee64..f8b94da2 100644 --- a/virtual-programs/apriltags.folk +++ b/virtual-programs/apriltags.folk @@ -35,38 +35,81 @@ When /someone/ detects tags /tags/ at /timestamp/ in time /aprilTime/ { } } -# On process { -# source pi/AprilTags.tcl -# AprilTags::init +On process { + source pi/AprilTags.tcl + AprilTags::init + + # TODO: Clean this up. We retract these so that we don't bounce + # statements back to the main Folk process that it sends us. + Retract /anyone/ wishes $::thisProcess shares all wishes + Retract /anyone/ wishes $::thisProcess shares all claims + Wish $::thisProcess receives statements like \ + [list /someone/ claims the camera frame is /grayFrame/ at /timestamp/] + Wish $::thisProcess receives statements like \ + [list /someone/ claims /process/ detects tags /tags/ at /timestamp/ in time /aprilTime/] + Wish $::thisProcess shares statements like \ + [list /someone/ wishes /process/ receives statements like /pattern/] + Wish $::thisProcess shares statements like \ + [list /someone/ claims $::thisProcess has pid /pid/] + Wish $::thisProcess shares statements like \ + [list /someone/ claims $::thisProcess detects tags /tags/ at /timestamp/ in time /aprilTime/] + Wish $::thisProcess shares statements like \ + [list /someone/ wishes /something/ is labelled /text/] + Wish $::thisProcess shares statements like \ + [list /someone/ wishes /something/ displays camera slice /slice/] + + proc subimage {im x y subwidth subheight} { + dict with im { + set x [expr {int($x)}] + set y [expr {int($y)}] + set subdata [expr {$data + ($y*$width + $x) * $components}] + dict create \ + width [int $subwidth] \ + height [int $subheight] \ + components $components \ + bytesPerRow $bytesPerRow \ + data [format 0x%x $subdata] + } + } -# # TODO: Clean this up. We retract these so that we don't bounce -# # statements back to the main Folk process that it sends us. -# Retract /anyone/ wishes $::thisProcess shares all wishes -# Retract /anyone/ wishes $::thisProcess shares all claims -# Wish $::thisProcess receives statements like \ -# [list /someone/ claims the camera frame is /grayFrame/] -# Wish $::thisProcess shares statements like \ -# [list /someone/ wishes /process/ receives statements like /pattern/] -# Wish $::thisProcess shares statements like \ -# [list /someone/ claims tag /tag/ has center /center/ size /size/] -# Wish $::thisProcess shares statements like \ -# [list /someone/ claims tag /tag/ has corners /corners/] -# Wish $::thisProcess shares statements like \ -# [list /someone/ claims the AprilTag time is /aprilTime/] -# Wish $::thisProcess shares statements like \ -# [list /someone/ claims $::thisProcess has pid /pid/] + When the camera frame is /grayFrame/ at /timestamp/ & \ + /process/ detects tags /prevTags/ at /something/ in time /something/ { -# When the camera frame is /grayFrame/ { -# set aprilTime [time { -# set tags [AprilTags::detect $grayFrame] -# }] -# Commit { -# Claim the AprilTag time is $aprilTime + if {$process eq $::thisProcess} { return } -# foreach tag $tags { -# Claim tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size] -# Claim tag [dict get $tag id] has corners [dict get $tag corners] -# } -# } -# } -# } + set tags [list] + set frameWidth [dict get $grayFrame width] + set frameHeight [dict get $grayFrame height] + set aprilTime 0 + foreach prevTag $prevTags { + set size [dict get $prevTag size] + set corners [dict get $prevTag corners] + set x [min {*}[lmap corner $corners {lindex $corner 0}]] + set y [min {*}[lmap corner $corners {lindex $corner 1}]] + set x1 [max {*}[lmap corner $corners {lindex $corner 0}]] + set y1 [max {*}[lmap corner $corners {lindex $corner 1}]] + + set x [max [- $x $size] 0] + set y [max [- $y $size] 0] + set x1 [min [+ $x1 $size] $frameWidth] + set y1 [min [+ $y1 $size] $frameHeight] + + set subimage [subimage $grayFrame $x $y [- $x1 $x] [- $y1 $y]] + Wish 6 displays camera slice $subimage + set aprilTime [+ $aprilTime [baretime { + lappend tags {*}[AprilTags::detect $subimage] + }]] + } + + Wish 6 is labelled "[llength $prevTags] -> [llength $tags] ($aprilTime us)" + + # Commit { + # Claim the AprilTag time is $aprilTime + + # foreach tag $tags { + # Claim tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size] + # Claim tag [dict get $tag id] has corners [dict get $tag corners] + # } + # } + } +} -- cgit v1.2.3 From 25ca25946df5fca30de60d477301f750d5394db7 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 14 Aug 2023 20:48:05 -0400 Subject: c: Use the Tcl error in __ENSURE_OK --- lib/c.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/c.tcl b/lib/c.tcl index 165ab723..82b1cfce 100644 --- a/lib/c.tcl +++ b/lib/c.tcl @@ -45,7 +45,7 @@ namespace eval c { #include #define __ENSURE(EXPR) if (!(EXPR)) { Tcl_SetResult(interp, "failed to convert argument from Tcl to C in: " #EXPR, NULL); return TCL_ERROR; } - #define __ENSURE_OK(EXPR) if ((EXPR) != TCL_OK) { Tcl_SetResult(interp, "failed to convert argument from Tcl to C in: " #EXPR, NULL); return TCL_ERROR; } + #define __ENSURE_OK(EXPR) if ((EXPR) != TCL_OK) { return TCL_ERROR; } } variable code [list] variable objtypes [list] -- cgit v1.2.3 From b9ceecfd47988d11952c34d40b63d16494fc8070 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 14 Aug 2023 20:48:17 -0400 Subject: evaluator: Add dot and print helpers --- lib/evaluator.tcl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/evaluator.tcl b/lib/evaluator.tcl index 0554c189..feded645 100644 --- a/lib/evaluator.tcl +++ b/lib/evaluator.tcl @@ -757,6 +757,15 @@ namespace eval Statements { ;# singleton Statement store } return "digraph { rankdir=LR; [join $dot "\n"] }" } + proc saveDotToPdf {filename} { + exec dot -Tpdf >$filename <<[Statements::dot] + } + + proc print {} { + dict for {id stmt} [Statements::all] { + puts [statement short $stmt] + } + } # these are kind of arbitrary/temporary bridge $cc proc matchRemoveFirstDestructor {match_handle_t matchId} void { -- cgit v1.2.3 From 5f08c1da793d98ce1269d7a9a47ec191024f9356 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 14 Aug 2023 20:48:50 -0400 Subject: Evaluator: better incrementalize collect --- lib/evaluator.tcl | 24 +++++++++++++++++++----- test/survival.tcl | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 test/survival.tcl diff --git a/lib/evaluator.tcl b/lib/evaluator.tcl index feded645..7a529666 100644 --- a/lib/evaluator.tcl +++ b/lib/evaluator.tcl @@ -892,6 +892,7 @@ namespace eval Evaluator { } } static void LogWriteRecollect(statement_handle_t collectId); + static void LogWriteUnmatch(match_handle_t matchId); void reactToStatementAdditionThatMatchesCollect(Tcl_Interp* interp, statement_handle_t collectId, Tcl_Obj* collectPattern, @@ -1017,8 +1018,7 @@ namespace eval Evaluator { match_handle_t matchId = edge->match; if (!matchExists(matchId)) continue; // if was removed earlier - reactToMatchRemoval(interp, matchId); - matchRemove(matchId); + LogWriteUnmatch(matchId); } } } @@ -1110,8 +1110,7 @@ namespace eval Evaluator { if (edge->type == CHILD && !matchHandleIsEqual(edge->match, matchId)) { match_handle_t childMatchId = edge->match; matchGet(childMatchId)->recollectOnDestruction = false; - reactToMatchRemoval(interp, childMatchId); - matchRemove(childMatchId); + LogWriteUnmatch(childMatchId); break; } } @@ -1120,7 +1119,7 @@ namespace eval Evaluator { $cc code { typedef enum { - NONE, ASSERT, RETRACT, SAY, RECOLLECT + NONE, ASSERT, RETRACT, SAY, UNMATCH, RECOLLECT } log_entry_op_t; typedef struct log_entry_t { log_entry_op_t op; @@ -1131,6 +1130,7 @@ namespace eval Evaluator { match_handle_t parentMatchId; Tcl_Obj* clause; } say; + struct { match_handle_t matchId; } unmatch; struct { statement_handle_t collectId; } recollect; }; } log_entry_t; @@ -1146,6 +1146,7 @@ namespace eval Evaluator { evaluatorLogReadIndex = (evaluatorLogReadIndex + 1) % EVALUATOR_LOG_CAPACITY; if (entry.op == ASSERT) { + /* printf("Assert (%s)\n", Tcl_GetString(entry.assert.clause)); */ statement_handle_t id; bool isNewStatement; addImpl(interp, entry.assert.clause, 0, NULL, &id, &isNewStatement); @@ -1155,6 +1156,7 @@ namespace eval Evaluator { Tcl_DecrRefCount(entry.assert.clause); } else if (entry.op == RETRACT) { + /* printf("Retract (%s)\n", Tcl_GetString(entry.retract.pattern)); */ environment_t* results[1000]; int resultsCount = searchByPattern(entry.retract.pattern, 1000, results); @@ -1167,6 +1169,7 @@ namespace eval Evaluator { Tcl_DecrRefCount(entry.retract.pattern); } else if (entry.op == SAY) { + /* printf("Say (%s)\n", Tcl_GetString(entry.say.clause)); */ if (matchExists(entry.say.parentMatchId)) { statement_handle_t id; bool isNewStatement; addImpl(interp, entry.say.clause, 1, &entry.say.parentMatchId, @@ -1177,7 +1180,15 @@ namespace eval Evaluator { } Tcl_DecrRefCount(entry.say.clause); + } else if (entry.op == UNMATCH) { + /* printf("Unmatch (m%d:%d)\n", entry.unmatch.matchId.idx, entry.unmatch.matchId.gen); */ + if (matchExists(entry.unmatch.matchId)) { + reactToMatchRemoval(interp, entry.unmatch.matchId); + matchRemove(entry.unmatch.matchId); + } + } else if (entry.op == RECOLLECT) { + /* printf("Recollect (s%d:%d)\n", entry.recollect.collectId.idx, entry.recollect.collectId.gen); */ if (exists(entry.recollect.collectId)) { recollect(interp, entry.recollect.collectId); } @@ -1208,6 +1219,9 @@ namespace eval Evaluator { Tcl_IncrRefCount(clause); LogWriteFront((log_entry_t) { .op = SAY, .say = {.parentMatchId=parentMatchId, .clause=clause} }); } + $cc proc LogWriteUnmatch {match_handle_t matchId} void { + LogWriteBack((log_entry_t) { .op = UNMATCH, .unmatch = {.matchId=matchId} }); + } $cc proc LogWriteRecollect {statement_handle_t collectId} void { LogWriteBack((log_entry_t) { .op = RECOLLECT, .recollect = {.collectId=collectId} }); } diff --git a/test/survival.tcl b/test/survival.tcl new file mode 100644 index 00000000..821b865a --- /dev/null +++ b/test/survival.tcl @@ -0,0 +1,23 @@ +Assert when we are running {{} { + When the collected matches for [list tag /k/ is visible] are /matches/ { + foreach m $matches { + Claim a tag is visible + } + puts "Collecting: [llength $matches] matches" + } + When a tag is visible { + On unmatch { + puts "Unmatching. Should not unmatch" + } + } +}} +Assert we are running +Step + +Assert tag 1 is visible +Step + +Assert tag 2 is visible +Assert tag 3 is visible +Retract tag 1 is visible +Step -- cgit v1.2.3 From f5f8719a3d13e6dcd5efe6fac1c9d52f0d891135 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 14 Aug 2023 21:03:45 -0400 Subject: apriltags: WIP: First working incremental detector --- main.tcl | 1 + virtual-programs/apriltags.folk | 60 ++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/main.tcl b/main.tcl index 70a5034d..f62bcd4a 100644 --- a/main.tcl +++ b/main.tcl @@ -133,6 +133,7 @@ proc On {event args} { # send that to the subprocess. lassign [uplevel Evaluator::serializeEnvironment] argNames argValues uplevel [list On-process $name [list apply [list $argNames $body] {*}$argValues]] + set name ;# Return the name to the caller in case they want it. } elseif {$event eq "unmatch"} { set body [lindex $args 0] diff --git a/virtual-programs/apriltags.folk b/virtual-programs/apriltags.folk index f8b94da2..d9557644 100644 --- a/virtual-programs/apriltags.folk +++ b/virtual-programs/apriltags.folk @@ -1,6 +1,7 @@ if {$::isLaptop} return -On process { +# Plain detector. Runs on entire camera frame. +set mainDetectorProcess [On process { source pi/AprilTags.tcl AprilTags::init @@ -21,20 +22,12 @@ On process { set aprilTime [time { set tags [AprilTags::detect $grayFrame] }] - Commit { - Claim $::thisProcess detects tags $tags at $timestamp in time $aprilTime - } + Claim $::thisProcess detects tags $tags at $timestamp in time $aprilTime } -} - -When /someone/ detects tags /tags/ at /timestamp/ in time /aprilTime/ { - Claim the AprilTag time is $aprilTime - foreach tag $tags { - Claim tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size] - Claim tag [dict get $tag id] has corners [dict get $tag corners] - } -} +}] +# Incremental detector. Looks at regions where there were tags in the +# old camera frame. On process { source pi/AprilTags.tcl AprilTags::init @@ -46,7 +39,7 @@ On process { Wish $::thisProcess receives statements like \ [list /someone/ claims the camera frame is /grayFrame/ at /timestamp/] Wish $::thisProcess receives statements like \ - [list /someone/ claims /process/ detects tags /tags/ at /timestamp/ in time /aprilTime/] + [list /someone/ claims $mainDetectorProcess detects tags /tags/ at /timestamp/ in time /aprilTime/] Wish $::thisProcess shares statements like \ [list /someone/ wishes /process/ receives statements like /pattern/] Wish $::thisProcess shares statements like \ @@ -95,21 +88,40 @@ On process { set y1 [min [+ $y1 $size] $frameHeight] set subimage [subimage $grayFrame $x $y [- $x1 $x] [- $y1 $y]] - Wish 6 displays camera slice $subimage set aprilTime [+ $aprilTime [baretime { - lappend tags {*}[AprilTags::detect $subimage] + foreach tag [AprilTags::detect $subimage] { + dict with tag { + set center [vec2 add $center [list $x $y]] + set corners [lmap corner $corners {vec2 add $corner [list $x $y]}] + } + lappend tags $tag + } }]] } - Wish 6 is labelled "[llength $prevTags] -> [llength $tags] ($aprilTime us)" + # Wish 6 is labelled "[llength $prevTags] -> [llength $tags] ($aprilTime us)" - # Commit { - # Claim the AprilTag time is $aprilTime + Claim $::thisProcess detects tags $tags at $timestamp in time $aprilTime + } +} + +When the collected matches for [list /someone/ detects tags /tags/ at /timestamp/ in time /aprilTime/] are /matches/ { + # Find all tag IDs, then find the latest for each ID. + # Sort by timestamp, earliest first. + proc mcomp {a b} {> [dict get $a timestamp] [dict get $b timestamp]} + set matches [lsort -command mcomp $matches] + + set tagsSeen [dict create] + foreach match $matches { + set tags [dict get $match tags] + foreach tag $tags { + dict set tagsSeen [dict get $tag id] $tag + } + } - # foreach tag $tags { - # Claim tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size] - # Claim tag [dict get $tag id] has corners [dict get $tag corners] - # } - # } + dict for {id tag} $tagsSeen { + Claim tag $id has center [dict get $tag center] size [dict get $tag size] + Claim tag $id has corners [dict get $tag corners] } + Claim the AprilTag time is [lmap m $matches {dict get $m aprilTime}] } -- cgit v1.2.3 From cf16ae229066352c3194e8b225bdf9b996a40738 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 14 Aug 2023 22:56:16 -0400 Subject: Try to fix collect incremental --- lib/evaluator.tcl | 3 ++- test/survival.tcl | 24 +++++++++++++----------- virtual-programs/tags-and-calibration.folk | 3 ++- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/evaluator.tcl b/lib/evaluator.tcl index 7a529666..02d87faa 100644 --- a/lib/evaluator.tcl +++ b/lib/evaluator.tcl @@ -1141,6 +1141,7 @@ namespace eval Evaluator { int evaluatorLogWriteIndex = 0; } $cc proc Evaluate {Tcl_Interp* interp} void { + /* printf("Evaluate==========\n"); */ while (evaluatorLogReadIndex != evaluatorLogWriteIndex) { log_entry_t entry = evaluatorLog[evaluatorLogReadIndex]; evaluatorLogReadIndex = (evaluatorLogReadIndex + 1) % EVALUATOR_LOG_CAPACITY; @@ -1223,7 +1224,7 @@ namespace eval Evaluator { LogWriteBack((log_entry_t) { .op = UNMATCH, .unmatch = {.matchId=matchId} }); } $cc proc LogWriteRecollect {statement_handle_t collectId} void { - LogWriteBack((log_entry_t) { .op = RECOLLECT, .recollect = {.collectId=collectId} }); + LogWriteFront((log_entry_t) { .op = RECOLLECT, .recollect = {.collectId=collectId} }); } $cc proc LogIsEmpty {} bool { return evaluatorLogReadIndex == evaluatorLogWriteIndex; diff --git a/test/survival.tcl b/test/survival.tcl index 821b865a..914ab45d 100644 --- a/test/survival.tcl +++ b/test/survival.tcl @@ -1,23 +1,25 @@ Assert when we are running {{} { - When the collected matches for [list tag /k/ is visible] are /matches/ { + When the collected matches for [list tag /k/ was seen by /x/ at /p/] are /matches/ { + set tagsSeen [dict create] foreach m $matches { - Claim a tag is visible + dict set tagsSeen [dict get $m k] true } - puts "Collecting: [llength $matches] matches" + dict for {k _} $tagsSeen { Claim tag $k is a tag } } - When a tag is visible { - On unmatch { - puts "Unmatching. Should not unmatch" - } + When tag /k/ is a tag { + puts "Saw tag $k" + On unmatch { error "Should never unmatch" } } }} Assert we are running Step -Assert tag 1 is visible +Commit Omar { Claim tag 1 was seen by Omar at home } +Commit Mom { Claim tag 1 was seen by Mom at restaurant } Step -Assert tag 2 is visible -Assert tag 3 is visible -Retract tag 1 is visible +Commit Omar { Claim tag 1 was seen by Omar at work } Step + +# Statements::print + diff --git a/virtual-programs/tags-and-calibration.folk b/virtual-programs/tags-and-calibration.folk index afb4aef4..b219b541 100644 --- a/virtual-programs/tags-and-calibration.folk +++ b/virtual-programs/tags-and-calibration.folk @@ -119,7 +119,8 @@ When (non-capturing) tag /tag/ has center /c/ size /size/ { } When (non-capturing) tag /tag/ is a tag { - puts "Added tag $tag" + puts "Added tag $tag" + On unmatch { puts "Removed tag $tag" } set tempPath "$::env(HOME)/folk-printed-programs/$tag.folk.temp" -- cgit v1.2.3 From 0dcb612ee6e7e5dd23778a2be41762a7e01b5e6d Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 14 Aug 2023 23:03:05 -0400 Subject: Bring tags down to 1 thread --- pi/AprilTags.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pi/AprilTags.tcl b/pi/AprilTags.tcl index 92e9cb71..69079813 100644 --- a/pi/AprilTags.tcl +++ b/pi/AprilTags.tcl @@ -17,7 +17,7 @@ namespace eval AprilTags { td = apriltag_detector_create(); tf = tagStandard52h13_create(); apriltag_detector_add_family_bits(td, tf, 1); - td->nthreads = 2; + td->nthreads = 1; } apc proc detect {image_t gray} Tcl_Obj* { -- cgit v1.2.3 From 986aa91a7483efa686eae99d218edaf2afbded42 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 14 Aug 2023 23:49:28 -0400 Subject: Use old positions so tags move 'monotonically' --- virtual-programs/apriltags.folk | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/virtual-programs/apriltags.folk b/virtual-programs/apriltags.folk index d9557644..1f38f29b 100644 --- a/virtual-programs/apriltags.folk +++ b/virtual-programs/apriltags.folk @@ -105,17 +105,29 @@ On process { } } -When the collected matches for [list /someone/ detects tags /tags/ at /timestamp/ in time /aprilTime/] are /matches/ { - # Find all tag IDs, then find the latest for each ID. - # Sort by timestamp, earliest first. - proc mcomp {a b} {> [dict get $a timestamp] [dict get $b timestamp]} - set matches [lsort -command mcomp $matches] +# This cache is used to remember the last seen position of each tag, +# so that if the incremental detector blinks out, we still use the +# tag's last-found position from it, instead of the older position +# from the full detector, so as you move a tag its position doesn't +# glitch backward. +set ::tagsCache [dict create] +# TODO: Garbage-collect this cache. +When the collected matches for [list /someone/ detects tags /tags/ at /timestamp/ in time /aprilTime/] are /matches/ { set tagsSeen [dict create] foreach match $matches { - set tags [dict get $match tags] - foreach tag $tags { - dict set tagsSeen [dict get $tag id] $tag + set timestamp [dict get $match timestamp] + foreach tag [dict get $match tags] { + set id [dict get $tag id] + dict set tag timestamp $timestamp + + if {[dict exists $::tagsCache $id] && + [dict get $::tagsCache $id timestamp] > $timestamp} { + set tag [dict get $::tagsCache $id] + } else { + dict set ::tagsCache $id $tag + } + dict set tagsSeen $id $tag } } -- cgit v1.2.3 From 330a2f7b72d56c841e2d99d1cbea4cc837bce641 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 10:29:19 -0400 Subject: Use CPU copy to drive display --- pi/Display.tcl | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/pi/Display.tcl b/pi/Display.tcl index 2c7c50ef..93e86739 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -54,12 +54,12 @@ dc code { static void commitThenClearStaging(); pixel_t* staging; + pixel_t* fbmem; struct { pixel_t* mem; uint32_t id; } fbs[2]; - int currentFbIndex; int fbwidth; int fbheight; @@ -197,12 +197,19 @@ dc proc setupGpu {} void { } setupFb(0); + fbmem = fbs[0].mem; setupFb(1); + staging = fbs[1].mem; - // Can't drop master if we're going to flip buffers at runtime. - // drmDropMaster(gpuFd); - - commitThenClearStaging(); + int ret = drmModeSetCrtc(gpuFd, gpuEnc->crtc_id, fbs[0].id, 0, 0, + &gpuConn->connector_id, 1, &gpuConn->modes[0]); + if (ret) { + fprintf(stderr, "Display: cannot flip CRTC to %d for connector %u (%d): %m\n", + 0, gpuConn->connector_id, errno); + exit(1); + } + + drmDropMaster(gpuFd); } dc proc setupFb {int idx} void [csubst { struct drm_mode_create_dumb dumb; @@ -238,19 +245,10 @@ dc proc setupFb {int idx} void [csubst { }] # Hack to support old stuff that uses framebuffer directly and doesn't commit. dc proc getFbPointer {} pixel_t* { - return fbs[currentFbIndex].mem; + return fbmem; } dc proc commitThenClearStaging {} void { - currentFbIndex = !currentFbIndex; - int ret = drmModeSetCrtc(gpuFd, gpuEnc->crtc_id, fbs[currentFbIndex].id, 0, 0, - &gpuConn->connector_id, 1, &gpuConn->modes[0]); - if (ret) { - fprintf(stderr, "Display: cannot flip CRTC to %d for connector %u (%d): %m\n", - currentFbIndex, - gpuConn->connector_id, errno); - exit(1); - } - staging = fbs[!currentFbIndex].mem; + memcpy(fbmem, staging, fbwidth * fbheight * sizeof(pixel_t)); // This memset takes ~2ms on 1080p on a Pi 4. memset(staging, 0, fbwidth * fbheight * sizeof(pixel_t)); } -- cgit v1.2.3 From b723ff80d288b659b455f86795acaf09c1b53a07 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 10:31:05 -0400 Subject: Display: RAM-allocate staging (10ms -> 5ms commit) --- pi/Display.tcl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pi/Display.tcl b/pi/Display.tcl index 93e86739..240afa4f 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -198,8 +198,7 @@ dc proc setupGpu {} void { setupFb(0); fbmem = fbs[0].mem; - setupFb(1); - staging = fbs[1].mem; + staging = ckalloc(fbwidth * fbheight * sizeof(pixel_t)); int ret = drmModeSetCrtc(gpuFd, gpuEnc->crtc_id, fbs[0].id, 0, 0, &gpuConn->connector_id, 1, &gpuConn->modes[0]); -- cgit v1.2.3 From 4c2f412686111adfc7f519ac6dc4d29f9d1bb6e3 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 13:41:42 -0400 Subject: web: Report frame time --- web.tcl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web.tcl b/web.tcl index 37081ba2..220d3337 100644 --- a/web.tcl +++ b/web.tcl @@ -68,9 +68,11 @@ proc handlePage {path contentTypeVar} { } set totalTimes [lsort -integer -stride 2 -index 1 $totalTimes] + set totalFrameTime 0 set l [list] foreach {body totalTime} $totalTimes { set runs [dict get $Evaluator::runsMap $body] + set totalFrameTime [expr {$totalFrameTime + $totalTime/$::stepCount}] lappend l [subst {
  • [htmlEscape $body]
    ($runs runs): [dict get $Evaluator::totalTimesMap $body]: $totalTime microseconds total ([expr {$totalTime/$::stepCount}] us per frame), $runs runs ([expr {$totalTime/$runs}] us per run; [expr {$runs/$::stepCount}] runs per frame) @@ -89,7 +91,7 @@ proc handlePage {path contentTypeVar} { statementClauseToId graph statements graph -

    Timings

    +

    Timings (sum per-frame time $totalFrameTime us)

      [join $l "\n"]
    } -- cgit v1.2.3 From b4d02a54b20c81ed73500c73da3af4bf8d6a9f89 Mon Sep 17 00:00:00 2001 From: Charles Chamberlain Date: Tue, 15 Aug 2023 15:44:39 -0400 Subject: Add Charles wifi --- hosts.tcl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosts.tcl b/hosts.tcl index 015be2f4..26f1b6c3 100644 --- a/hosts.tcl +++ b/hosts.tcl @@ -36,6 +36,8 @@ if {[info exists ::env(FOLK_SHARE_NODE)]} { set ::shareNode "folk-dpip.local" } elseif {$wifi eq "interact residency"} { set ::shareNode "folk-interact.local" + } elseif {$wifi eq "Fios-gLwY5" } { + set ::shareNode "folk-charles.local" } else { # there's no default. } -- cgit v1.2.3 From f42902b4cf694a4ec3f4f345699384ab53e32ece Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 17:14:19 -0400 Subject: WIP: Rewrite peering to use shm instead of websockets Huge performance increases, but crashy. --- lib/peer.tcl | 65 +++++------------------ lib/process.tcl | 13 +++-- main.tcl | 115 ++++++++++++++++++++++++++++++++++------ virtual-programs/apriltags.folk | 1 + virtual-programs/camera.folk | 3 +- virtual-programs/display.folk | 6 ++- virtual-programs/time.folk | 2 +- 7 files changed, 128 insertions(+), 77 deletions(-) diff --git a/lib/peer.tcl b/lib/peer.tcl index 274f9f07..0a1aef0d 100644 --- a/lib/peer.tcl +++ b/lib/peer.tcl @@ -25,72 +25,31 @@ namespace eval ::Peers {} set ::peersBlacklist [dict create] proc ::peer {process {dieOnDisconnect false}} { - package require websocket namespace eval ::Peers::$process { - variable connected false + variable connected true proc log {s} { variable process puts "$::thisProcess -> $process: $s" } - proc setupSock {} { - variable process - log "Trying to connect to: ws://$process:4273/ws" - variable chan [::websocket::open "ws://$process:4273/ws" [namespace code handleWs]] - } - proc handleWs {chan type msg} { - if {$type eq "connect"} { - log "Connected" - variable connected true - - # Establish a peering on their end, in the reverse - # direction, so they can send stuff back to us. - # It'll implicitly run in a ::Peers::X namespace on their end - # (because of how `run` is implemented below) - run { - set name [namespace tail [namespace current]] - variable chan [uplevel {set chan}] - - # First, check if this side has us blacklisted. - if {[dict exists $::peersBlacklist $name]} { - ::websocket::close $chan - return - } - - variable connected true - proc run {msg} { - variable chan - ::websocket::send $chan text $msg - } - } - } elseif {$type eq "disconnect"} { - log "Disconnected" - variable dieOnDisconnect - if {$dieOnDisconnect} { exit 0 } + # TODO: Handle die on disconnect (?) - variable connected false - after 2000 [namespace code setupSock] - } elseif {$type eq "error"} { - log "WebSocket error: $type $msg" - after 2000 [namespace code setupSock] - } elseif {$type eq "text"} { - eval $msg - } elseif {$type eq "ping" || $type eq "pong"} { - } else { - error "Unknown WebSocket event: $type $msg" - } + proc share {statements} { + variable process + Mailbox::share $::thisProcess $process $statements } - - proc run {msg} { - variable chan - ::websocket::send $chan text [list namespace eval ::Peers::$::thisProcess $msg] + proc receive {} { + variable process + Mailbox::receive $process $::thisProcess } proc init {n shouldDieOnDisconnect} { - variable process $n; setupSock + variable process $n variable dieOnDisconnect $shouldDieOnDisconnect - vwait ::Peers::${n}::connected + + Mailbox::create $::thisProcess $process + Mailbox::create $process $::thisProcess } init } $process $dieOnDisconnect diff --git a/lib/process.tcl b/lib/process.tcl index a4b8ed48..059c0b89 100644 --- a/lib/process.tcl +++ b/lib/process.tcl @@ -58,19 +58,22 @@ namespace eval ::Zygote { proc On-process {name body} { set this [uplevel {expr {[info exists this] ? $this : ""}}] - set processCode [list apply {{__name __body} { + set processCode [list apply {{__parentProcess __name __body} { set ::thisProcess $__name Assert wishes $::thisProcess shares all wishes Assert wishes $::thisProcess shares all claims - ::peer "localhost" true + ::peer $__parentProcess true Assert claims $::thisProcess has pid [pid] Assert when $::thisProcess has pid /something/ [list {} $__body] - Step - vwait forever - }} $name $body] + while true { + Step + } + }} $::thisProcess $name $body] + + ::peer $name false Zygote::spawn [list apply {{processCode} { # A supervisor that wraps the subprocess. diff --git a/main.tcl b/main.tcl index f62bcd4a..484aae64 100644 --- a/main.tcl +++ b/main.tcl @@ -166,8 +166,6 @@ proc Commit {args} { set lambda [list {this} [list apply [list $argNames $body] {*}$argValues]] dict set ::toCommit $key $lambda } - - after idle Step } set ::stepCount 0 @@ -194,6 +192,9 @@ proc StepImpl {} { Evaluator::Evaluate } + set ::peerTime [baretime { + + # This takes 2 ms. set shareStatements [clauseset create] set shareAllWishes [expr {[llength [Statements::findMatches [list /someone/ wishes $::thisProcess shares all wishes]]] > 0}] set shareAllClaims [expr {[llength [Statements::findMatches [list /someone/ wishes $::thisProcess shares all claims]]] > 0}] @@ -225,29 +226,31 @@ proc StepImpl {} { variable connected if {!$connected} { return } + # Receive. + Commit $peer [list Say $peer is sharing statements [receive]] + + # Share. ::addMatchesToShareStatements shareStatements \ [Statements::findMatches [list /someone/ wishes $peer receives statements like /pattern/]] - if {![info exists prevShareStatements] || ([clauseset size $prevShareStatements] > 0 || [clauseset size $shareStatements] > 0)} { - run [list apply {{process receivedStatements} { - upvar chan chan - Commit $chan statements { - Say $process is sharing statements $receivedStatements - } - }} $::thisProcess [clauseset clauses $shareStatements]] + share [clauseset clauses $shareStatements] set prevShareStatements $shareStatements } } $peerNs] [namespace tail $peerNs] $shareStatements } + + }] } + proc Step {} { if {[dict size $::toCommit] > 0 || ![Evaluator::LogIsEmpty]} { - set ::stepTime [baretime StepImpl] + set stepTime [baretime StepImpl] + set ::stepTime "$stepTime us (peer $::peerTime us)" } } @@ -285,8 +288,10 @@ namespace eval ::Heap { $cc include $cc include $cc include + $cc include + $cc include $cc code { - size_t folkHeapSize = 100000000; // 100MB + size_t folkHeapSize = 400000000; // 400MB uint8_t* folkHeapBase; uint8_t* _Atomic folkHeapPointer; } @@ -294,18 +299,20 @@ namespace eval ::Heap { # subprocesses, since it's established before the creation of # the zygote. $cc proc folkHeapMount {} void { + shm_unlink("/folk-heap"); int fd = shm_open("/folk-heap", O_RDWR | O_CREAT, S_IROTH | S_IWOTH | S_IRUSR | S_IWUSR); - ftruncate(fd, folkHeapSize); + if (fd == -1) { fprintf(stderr, "folkHeapMount: shm_open failed\n"); exit(1); } + if (ftruncate(fd, folkHeapSize) == -1) { fprintf(stderr, "folkHeapMount: ftruncate failed\n"); exit(1); } folkHeapBase = (uint8_t*) mmap(0, folkHeapSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (folkHeapBase == NULL) { - fprintf(stderr, "heapMount: failed"); exit(1); + if (folkHeapBase == NULL || folkHeapBase == (void *) -1) { + fprintf(stderr, "folkHeapMount: mmap failed: '%s'\n", strerror(errno)); exit(1); } folkHeapPointer = folkHeapBase; } $cc proc folkHeapAlloc {size_t sz} void* { if (folkHeapPointer + sz > folkHeapBase + folkHeapSize) { - fprintf(stderr, "heapAlloc: out of memory"); exit(1); + fprintf(stderr, "folkHeapAlloc: out of memory\n"); exit(1); } void* ptr = folkHeapPointer; folkHeapPointer = folkHeapPointer + sz; @@ -321,6 +328,84 @@ namespace eval ::Heap { } Heap::init +namespace eval ::Mailbox { + set cc [c create] + $cc include + $cc include + $cc include + $cc import ::Heap::cc folkHeapAlloc as folkHeapAlloc + $cc code { + typedef struct mailbox_t { + bool active; + + pthread_mutex_t mutex; + + char from[100]; + char to[100]; + + bool received; + char mail[1000000]; + } mailbox_t; + + #define NMAILBOXES 100 + mailbox_t* mailboxes; + } + $cc proc init {} void { + mailboxes = folkHeapAlloc(sizeof(mailbox_t) * NMAILBOXES); + printf("mailboxes = %p\n", mailboxes); + } + $cc proc create {char* from char* to} void { + if (find(from, to) != NULL) return; + for (int i = 0; i < NMAILBOXES; i++) { + if (!mailboxes[i].active) { + mailboxes[i].active = true; + pthread_mutex_init(&mailboxes[i].mutex, NULL); + snprintf(mailboxes[i].from, 100, "%s", from); + snprintf(mailboxes[i].to, 100, "%s", to); + mailboxes[i].mail[0] = '\0'; + return; + } + } + fprintf(stderr, "Out of available mailboxes.\n"); + exit(1); + } + $cc code { + mailbox_t* find(char* from, char* to) { + for (int i = 0; i < NMAILBOXES; i++) { + if (mailboxes[i].active && + strcmp(mailboxes[i].from, from) == 0 && + strcmp(mailboxes[i].to, to) == 0) { + return &mailboxes[i]; + } + } + return NULL; + } + } + $cc proc share {char* from char* to char* statements} void { + mailbox_t* mailbox = find(from, to); + if (!mailbox) { + fprintf(stderr, "Could not find mailbox for '%s -> %s'.\n", from, to); + exit(1); + } + pthread_mutex_lock(&mailbox->mutex); { + mailbox->received = false; + snprintf(mailbox->mail, sizeof(mailbox->mail), "%s", statements); + } pthread_mutex_unlock(&mailbox->mutex); + } + $cc proc receive {char* from char* to} Tcl_Obj* { + mailbox_t* mailbox = find(from, to); + if (!mailbox) { return Tcl_NewStringObj("", -1); } + Tcl_Obj* ret; + pthread_mutex_lock(&mailbox->mutex); { + mailbox->received = true; + ret = Tcl_NewStringObj(mailbox->mail, -1); + } pthread_mutex_unlock(&mailbox->mutex); + return ret; + } + $cc compile + init +} + if {[info exists ::entry]} { source "lib/process.tcl" Zygote::init diff --git a/virtual-programs/apriltags.folk b/virtual-programs/apriltags.folk index 1f38f29b..ac2c4f8b 100644 --- a/virtual-programs/apriltags.folk +++ b/virtual-programs/apriltags.folk @@ -102,6 +102,7 @@ On process { # Wish 6 is labelled "[llength $prevTags] -> [llength $tags] ($aprilTime us)" Claim $::thisProcess detects tags $tags at $timestamp in time $aprilTime + # Wish 6 is labelled "\n\nStep time $::stepTime\n[join [lmap s [dict values [Statements::all]] {statement short $s}] "\n"]" } } diff --git a/virtual-programs/camera.folk b/virtual-programs/camera.folk index 4f59a576..225a5880 100644 --- a/virtual-programs/camera.folk +++ b/virtual-programs/camera.folk @@ -20,7 +20,7 @@ On process { puts "Camera tid: [getTid] booting at [clock milliseconds]" - forever { + while true { set cameraTime [time { set grayFrame [Camera::grayFrame] }] @@ -28,6 +28,7 @@ On process { Claim the camera time is $cameraTime Claim the camera frame is $grayFrame at [clock milliseconds] } + Step } } diff --git a/virtual-programs/display.folk b/virtual-programs/display.folk index 5e16797f..b0b878ca 100644 --- a/virtual-programs/display.folk +++ b/virtual-programs/display.folk @@ -60,7 +60,7 @@ On process { [list /someone/ claims the display time is /displayTime/] set ::frames [list] - forever { + while true { set displayList [list] foreach match [Statements::findMatches {/someone/ wishes display runs /command/ on layer /layer/}] { lappend displayList [list [dict get $match layer] [dict get $match command]] @@ -93,7 +93,9 @@ On process { } } set frames [lreplace $frames 0 end-$framesInLastSecond] - Commit { Claim the display time is "render $renderTime us + commit $commitTime us ($framesInLastSecond fps)" } + + Commit { Claim the display time is "render $renderTime us + commit $commitTime us ($::stepTime) ($framesInLastSecond fps)" } + Step } } # TODO: remove this compatibility hack diff --git a/virtual-programs/time.folk b/virtual-programs/time.folk index 961b1863..1dff734d 100644 --- a/virtual-programs/time.folk +++ b/virtual-programs/time.folk @@ -1,3 +1,3 @@ When $::thisProcess has step count /t/ { - Claim the clock time is [/ [clock milliseconds] 1000.0] + Claim the clock time is [/ [clock milliseconds] 1000.0] } -- cgit v1.2.3 From 2c865242a751437dd91d0905b5d1b69b11682414 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 17:22:00 -0400 Subject: WIP: Clear out mailboxes on boot --- main.tcl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.tcl b/main.tcl index 484aae64..f6fd739f 100644 --- a/main.tcl +++ b/main.tcl @@ -351,8 +351,10 @@ namespace eval ::Mailbox { mailbox_t* mailboxes; } $cc proc init {} void { + fprintf(stderr, "Before: mailboxes = %p\n", mailboxes); mailboxes = folkHeapAlloc(sizeof(mailbox_t) * NMAILBOXES); - printf("mailboxes = %p\n", mailboxes); + memset(mailboxes, 0, sizeof(mailbox_t) * NMAILBOXES); + fprintf(stderr, "After: mailboxes = %p\n", mailboxes); } $cc proc create {char* from char* to} void { if (find(from, to) != NULL) return; -- cgit v1.2.3 From 43016b5705f53bc9ba913e11b8cead9da605348e Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 17:51:21 -0400 Subject: Hack: Kill all old processes on boot Fixes terrible issues bc die-on-disconnect doesn't exist yet --- main.tcl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.tcl b/main.tcl index f6fd739f..0a59313a 100644 --- a/main.tcl +++ b/main.tcl @@ -409,6 +409,10 @@ namespace eval ::Mailbox { } if {[info exists ::entry]} { + # TODO: Fix this hack. + set thisPid [pid] + foreach pid [exec pgrep tclsh8.6] { if {$pid ne $thisPid} { exec kill -9 $pid }} + source "lib/process.tcl" Zygote::init -- cgit v1.2.3 From f0d3ebec3acb73cdc83140ebaf50b53e36b8310f Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 19:00:04 -0400 Subject: Fix multiprocess mutex. This is usable now! --- main.tcl | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/main.tcl b/main.tcl index 0a59313a..e2e93bb3 100644 --- a/main.tcl +++ b/main.tcl @@ -1,5 +1,10 @@ if {$tcl_version eq 8.5} { error "Don't use Tcl 8.5 / macOS system Tcl. Quitting." } +# TODO: Fix this hack. +set thisPid [pid] +foreach pid [exec pgrep tclsh8.6] { if {$pid ne $thisPid} { exec kill -9 $pid } } +exec sleep 1 + if {[info exists ::argv0] && $::argv0 eq [info script]} { set ::isLaptop [expr {$tcl_platform(os) eq "Darwin" || ([info exists ::env(XDG_SESSION_TYPE)] && @@ -343,7 +348,7 @@ namespace eval ::Mailbox { char from[100]; char to[100]; - bool received; + int mailLen; char mail[1000000]; } mailbox_t; @@ -358,10 +363,16 @@ namespace eval ::Mailbox { } $cc proc create {char* from char* to} void { if (find(from, to) != NULL) return; + fprintf(stderr, "Mailbox create %s -> %s\n", from, to); for (int i = 0; i < NMAILBOXES; i++) { if (!mailboxes[i].active) { mailboxes[i].active = true; - pthread_mutex_init(&mailboxes[i].mutex, NULL); + + pthread_mutexattr_t mattr; + pthread_mutexattr_init(&mattr); + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&mailboxes[i].mutex, &mattr); + snprintf(mailboxes[i].from, 100, "%s", from); snprintf(mailboxes[i].to, 100, "%s", to); mailboxes[i].mail[0] = '\0'; @@ -390,8 +401,7 @@ namespace eval ::Mailbox { exit(1); } pthread_mutex_lock(&mailbox->mutex); { - mailbox->received = false; - snprintf(mailbox->mail, sizeof(mailbox->mail), "%s", statements); + mailbox->mailLen = snprintf(mailbox->mail, sizeof(mailbox->mail), "%s", statements); } pthread_mutex_unlock(&mailbox->mutex); } $cc proc receive {char* from char* to} Tcl_Obj* { @@ -399,8 +409,7 @@ namespace eval ::Mailbox { if (!mailbox) { return Tcl_NewStringObj("", -1); } Tcl_Obj* ret; pthread_mutex_lock(&mailbox->mutex); { - mailbox->received = true; - ret = Tcl_NewStringObj(mailbox->mail, -1); + ret = Tcl_NewStringObj(mailbox->mail, mailbox->mailLen); } pthread_mutex_unlock(&mailbox->mutex); return ret; } @@ -409,10 +418,6 @@ namespace eval ::Mailbox { } if {[info exists ::entry]} { - # TODO: Fix this hack. - set thisPid [pid] - foreach pid [exec pgrep tclsh8.6] { if {$pid ne $thisPid} { exec kill -9 $pid }} - source "lib/process.tcl" Zygote::init -- cgit v1.2.3 From 55254c4e66340507bc7e58aa45d03daaaea76e49 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Tue, 15 Aug 2023 19:04:01 -0400 Subject: Simplify web-image --- user-programs/haippi7/web-image.folk | 86 +----------------------------------- 1 file changed, 2 insertions(+), 84 deletions(-) diff --git a/user-programs/haippi7/web-image.folk b/user-programs/haippi7/web-image.folk index 8c437ce9..d06df1b3 100644 --- a/user-programs/haippi7/web-image.folk +++ b/user-programs/haippi7/web-image.folk @@ -1,91 +1,9 @@ -set cc [c create] -$cc cflags -L[lindex [exec /usr/sbin/ldconfig -p | grep libjpeg] end] - -# defineImageType $cc -# for some reason defineImageType doesn't work here so we do it manually -$cc code { - typedef struct { - uint32_t width; - uint32_t height; - int components; - uint32_t bytesPerRow; - - uint8_t *data; - } image_t; -} - -$cc argtype image_t { - image_t $argname; sscanf(Tcl_GetString($obj), "width %u height %u components %d bytesPerRow %u data 0x%p", &$argname.width, &$argname.height, &$argname.components, &$argname.bytesPerRow, &$argname.data); -} -$cc rtype image_t { - $robj = Tcl_ObjPrintf("width %u height %u components %d bytesPerRow %u data 0x%" PRIxPTR, $rvalue.width, $rvalue.height, $rvalue.components, $rvalue.bytesPerRow, (uintptr_t) $rvalue.data); -} - -$cc include -$cc include -$cc include - -$cc code { - #include - #include - #include - - void -jpeg(FILE* dest, uint8_t* rgb, uint32_t width, uint32_t height, int quality) -{ - JSAMPARRAY image; - image = calloc(height, sizeof (JSAMPROW)); - for (size_t i = 0; i < height; i++) { - image[i] = calloc(width * 3, sizeof (JSAMPLE)); - for (size_t j = 0; j < width; j++) { - image[i][j * 3 + 0] = rgb[(i * width + j)]; - image[i][j * 3 + 1] = rgb[(i * width + j)]; - image[i][j * 3 + 2] = rgb[(i * width + j)]; - } - } - - struct jpeg_compress_struct compress; - struct jpeg_error_mgr error; - compress.err = jpeg_std_error(&error); - jpeg_create_compress(&compress); - jpeg_stdio_dest(&compress, dest); - - compress.image_width = width; - compress.image_height = height; - compress.input_components = 3; - compress.in_color_space = JCS_RGB; - jpeg_set_defaults(&compress); - jpeg_set_quality(&compress, quality, TRUE); - jpeg_start_compress(&compress, TRUE); - jpeg_write_scanlines(&compress, image, height); - jpeg_finish_compress(&compress); - jpeg_destroy_compress(&compress); - - for (size_t i = 0; i < height; i++) { - free(image[i]); - } - free(image); -} - -} - - -$cc proc saveTempImage {image_t im char* filename} void { - // write capture to jpeg - // char filename[100] = "web-image-test.jpg"; - FILE* out = fopen(filename, "w"); - jpeg(out, im.data, im.width, im.height, 100); - fclose(out); -} -c loadlib [lindex [exec /usr/sbin/ldconfig -p | grep libjpeg] end] -$cc compile - When the camera frame is /im/ { Wish the web server handles route "/frame-image/$" with handler [list apply {{im} { # set width [dict get $im width] # set height [dict get $im height] set filename "/tmp/web-image-frame.jpg" - saveTempImage $im $filename + image saveAsJpeg $im $filename set fsize [file size $filename] set fd [open $filename r] fconfigure $fd -encoding binary -translation binary @@ -93,4 +11,4 @@ When the camera frame is /im/ { close $fd dict create statusAndHeaders "HTTP/1.1 200 OK\nConnection: close\nContent-Type: image/jpeg\nContent-Length: $fsize\n\n" body $body }} $im] -} \ No newline at end of file +} -- cgit v1.2.3 From b1c0ef9a2f5e412cda8a21b1efc1930101b088a0 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 16 Aug 2023 09:50:32 -0400 Subject: Increase log size + some unmatch hacking --- lib/evaluator.tcl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/evaluator.tcl b/lib/evaluator.tcl index 02d87faa..73c6a353 100644 --- a/lib/evaluator.tcl +++ b/lib/evaluator.tcl @@ -1031,6 +1031,7 @@ namespace eval Evaluator { if (unmatch->edges[j].type == PARENT) { statement_handle_t unmatchWhenId = unmatch->edges[j].statement; statement_t* unmatchWhen = get(unmatchWhenId); + if (unmatchWhen == NULL) continue; for (int k = 0; k < unmatchWhen->n_edges; k++) { if (unmatchWhen->edges[k].type == PARENT) { unmatchId = unmatchWhen->edges[k].match; @@ -1135,7 +1136,7 @@ namespace eval Evaluator { }; } log_entry_t; - log_entry_t evaluatorLog[1024] = {0}; + log_entry_t evaluatorLog[4096] = {0}; #define EVALUATOR_LOG_CAPACITY (sizeof(evaluatorLog)/sizeof(evaluatorLog[1])) int evaluatorLogReadIndex = EVALUATOR_LOG_CAPACITY - 1; int evaluatorLogWriteIndex = 0; -- cgit v1.2.3 From 05a416ced03c9e77bf29d12f04f67d12338bfeb1 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 16 Aug 2023 13:55:23 -0400 Subject: Support wish to display images. Make Folk allocator actually multiprocess-safe, make drawImageTransparent support components=3, make saveAsJpeg support components=3, add loadJpeg. --- main.tcl | 11 ++--- pi/Display.tcl | 94 ++++++++++++++++++++++++--------------- virtual-programs/images.folk | 102 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 155 insertions(+), 52 deletions(-) diff --git a/main.tcl b/main.tcl index e2e93bb3..51d87f3f 100644 --- a/main.tcl +++ b/main.tcl @@ -298,7 +298,7 @@ namespace eval ::Heap { $cc code { size_t folkHeapSize = 400000000; // 400MB uint8_t* folkHeapBase; - uint8_t* _Atomic folkHeapPointer; + uint8_t** _Atomic folkHeapPointer; } # The memory mapping of the heap will be inherited by all # subprocesses, since it's established before the creation of @@ -313,14 +313,15 @@ namespace eval ::Heap { if (folkHeapBase == NULL || folkHeapBase == (void *) -1) { fprintf(stderr, "folkHeapMount: mmap failed: '%s'\n", strerror(errno)); exit(1); } - folkHeapPointer = folkHeapBase; + folkHeapPointer = (uint8_t**) folkHeapBase; + *folkHeapPointer = folkHeapBase + sizeof(*folkHeapPointer); } $cc proc folkHeapAlloc {size_t sz} void* { - if (folkHeapPointer + sz > folkHeapBase + folkHeapSize) { + if (*folkHeapPointer + sz >= folkHeapBase + folkHeapSize) { fprintf(stderr, "folkHeapAlloc: out of memory\n"); exit(1); } - void* ptr = folkHeapPointer; - folkHeapPointer = folkHeapPointer + sz; + void* ptr = *folkHeapPointer; + *folkHeapPointer += sz; return (void*) ptr; } if {$::tcl_platform(os) eq "Linux"} { diff --git a/pi/Display.tcl b/pi/Display.tcl index 240afa4f..660056e1 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -346,15 +346,23 @@ dc proc drawCircle {int x0 int y0 int radius int color} void { defineImageType dc dc proc drawImageTransparent {int x0 int y0 image_t image int transparentTone int scale} void { - if (image.components != 1) { exit(1); } for (int y = 0; y < image.height; y++) { for (int x = 0; x < image.width; x++) { - // Index into image to get color - int i = y*image.bytesPerRow + x*image.components; + // index into image to get color + int i = y*image.bytesPerRow + x*image.components; (void)i; uint8_t r; uint8_t g; uint8_t b; - if (image.data[i] == transparentTone) { continue; } - r = image.data[i]; g = image.data[i]; b = image.data[i]; + if (image.components == 1) { + if (image.data[i] == transparentTone) { continue; } + r = image.data[i]; g = image.data[i]; b = image.data[i]; + } else if (image.components == 3) { + if (image.data[i] == transparentTone && + image.data[i + 1] == transparentTone && + image.data[i + 2] == transparentTone) { + continue; + } + r = image.data[i]; g = image.data[i + 1]; b = image.data[i + 2]; + } // Write repeatedly to framebuffer to scale up image for (int dy = 0; dy < scale; dy++) { @@ -365,44 +373,62 @@ dc proc drawImageTransparent {int x0 int y0 image_t image int transparentTone in if (sx < 0 || fbwidth <= sx || sy < 0 || fbheight <= sy) continue; staging[sy*fbwidth + sx] = PIXEL(r, g, b); - } } } } } -dc proc drawImage {int x0 int y0 image_t image int scale} void { - for (int y = 0; y < image.height; y++) { - for (int x = 0; x < image.width; x++) { - - // Index into image to get color - int i = y*image.bytesPerRow + x*image.components; - uint8_t r; uint8_t g; uint8_t b; - if (image.components == 3) { - r = image.data[i]; g = image.data[i+1]; b = image.data[i+2]; - } else if (image.components == 1) { - r = image.data[i]; g = image.data[i]; b = image.data[i]; - } else { - exit(1); - } +source "pi/rotate.tcl" +dc proc drawImage {int x0 int y0 image_t image double radians int scale} void { + + drawImageTransparent(x0 - image.width*scale/2, + y0 - image.height*scale/2, + image, 0x00, scale); + return; - // Write repeatedly to framebuffer to scale up image - for (int dy = 0; dy < scale; dy++) { - for (int dx = 0; dx < scale; dx++) { + double radiansNormalized = fmod(radians, 2.0 * M_PI); + if (radiansNormalized > M_PI) { + radiansNormalized -= 2.0 * M_PI; + } else if (radiansNormalized < -M_PI) { + radiansNormalized += 2.0 * M_PI; + } + int imageX; int imageY; + image_t temp = rotateMakeImage(image.width, image.height, image.components, + radiansNormalized, + &imageX, &imageY); + // Draw the image into the temp image. + for (int y = 0; y < image.height; y++) { + memcpy(&temp.data[(y + imageY) * temp.bytesPerRow + imageX * image.components], + &image.data[y * image.bytesPerRow], + image.bytesPerRow); + } + rotate(temp, imageX, imageY, image.width, image.height, radiansNormalized); - int sx = x0 + scale * x + dx; - int sy = y0 + scale * y + dy; - if (sx < 0 || fbwidth <= sx || sy < 0 || fbheight <= sy) continue; + // Find corners of rotated rectangle + Vec2i topLeft = Vec2i_rotate((Vec2i) {-(int)image.width/2, -(int)image.height/2}, radiansNormalized); + Vec2i topRight = Vec2i_rotate((Vec2i) {image.width/2, -(int)image.height/2}, radiansNormalized); + Vec2i bottomLeft = Vec2i_rotate((Vec2i) {-(int)image.width/2, image.height/2}, radiansNormalized); + Vec2i bottomRight = Vec2i_rotate((Vec2i) {image.width/2, image.height/2}, radiansNormalized); - staging[sy*fbwidth + sx] = PIXEL(r, g, b); + // Now blit the offscreen buffer to the screen. + image_t rotatedImage = { + .width = max4(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x) - + min4(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x), + .height = max4(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y) - + min4(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y), + .components = temp.components, + .bytesPerRow = temp.bytesPerRow + }; + int rotatedImageX0 = (temp.width - rotatedImage.width) / 2; + int rotatedImageY0 = (temp.height - rotatedImage.height) / 2; + rotatedImage.data = &temp.data[rotatedImageY0*temp.bytesPerRow + rotatedImageX0*temp.components]; - } - } - } - } + drawImageTransparent(x0 - rotatedImage.width*scale/2, + y0 - rotatedImage.height*scale/2, + rotatedImage, 0x00, scale); + ckfree(temp.data); } -source "pi/rotate.tcl" dc proc drawText {int x0 int y0 double radians int scale char* text} void { // Draws text (breaking at linebreaks), with the center of the // text at (x0, y0). Rotates counterclockwise up from the @@ -625,8 +651,8 @@ namespace eval Display { } } - proc image {x y im {scale 1.0}} { - drawImage [expr {int($x)}] [expr {int($y)}] $im [expr {int($scale)}] + proc image {x y im {radians 0} {scale 1.0}} { + drawImage [expr {int($x)}] [expr {int($y)}] $im $radians [expr {int($scale)}] } # for debugging diff --git a/virtual-programs/images.folk b/virtual-programs/images.folk index 6846975e..d98c58d0 100644 --- a/virtual-programs/images.folk +++ b/virtual-programs/images.folk @@ -32,6 +32,7 @@ namespace eval ::image { defineImageType $cc $cc include $cc include + $cc import ::Heap::cc folkHeapAlloc as folkHeapAlloc $cc code { #undef EXTERN @@ -40,18 +41,30 @@ namespace eval ::image { #include void - jpeg(FILE* dest, uint8_t* rgb, uint32_t width, uint32_t height, int quality) + jpeg(FILE* dest, uint8_t* data, uint32_t components, uint32_t width, uint32_t height, int quality) { - JSAMPARRAY image; - image = calloc(height, sizeof (JSAMPROW)); - for (size_t i = 0; i < height; i++) { - image[i] = calloc(width * 3, sizeof (JSAMPLE)); - for (size_t j = 0; j < width; j++) { - image[i][j * 3 + 0] = rgb[(i * width + j)]; - image[i][j * 3 + 1] = rgb[(i * width + j)]; - image[i][j * 3 + 2] = rgb[(i * width + j)]; + JSAMPARRAY image; + if (components == 1) { + image = calloc(height, sizeof (JSAMPROW)); + for (size_t i = 0; i < height; i++) { + image[i] = calloc(width * 3, sizeof (JSAMPLE)); + for (size_t j = 0; j < width; j++) { + image[i][j * 3 + 0] = data[(i * width + j)]; + image[i][j * 3 + 1] = data[(i * width + j)]; + image[i][j * 3 + 2] = data[(i * width + j)]; + } + } + } else if (components == 3) { + image = calloc(height, sizeof (JSAMPROW)); + for (size_t i = 0; i < height; i++) { + image[i] = calloc(width * 3, sizeof (JSAMPLE)); + for (size_t j = 0; j < width; j++) { + image[i][j * 3 + 0] = data[(i * width + j) * 3]; + image[i][j * 3 + 1] = data[(i * width + j) * 3 + 1]; + image[i][j * 3 + 2] = data[(i * width + j) * 3 + 2]; + } + } } - } struct jpeg_compress_struct compress; struct jpeg_error_mgr error; @@ -79,9 +92,48 @@ namespace eval ::image { } $cc proc saveAsJpeg {image_t im char* filename} void { FILE* out = fopen(filename, "w"); - jpeg(out, im.data, im.width, im.height, 100); + jpeg(out, im.data, im.components, im.width, im.height, 100); fclose(out); } + $cc proc loadJpeg {char* filename} image_t { + FILE* file = fopen(filename, "rb"); + if (!file) { + fprintf(stderr, "Error opening file: %s\n", filename); + exit(1); + } + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, file); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + image_t ret; + ret.width = cinfo.output_width; + ret.height = cinfo.output_height; + ret.components = cinfo.output_components; + ret.bytesPerRow = ret.width * ret.components; + ret.data = folkHeapAlloc(ret.bytesPerRow * ret.height); + + JSAMPROW row_pointer[1]; + while (cinfo.output_scanline < cinfo.output_height) { + row_pointer[0] = (JSAMPLE*)ret.data + cinfo.output_scanline * ret.bytesPerRow; + jpeg_read_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(file); + + return ret; + } + $cc proc freeJpeg {image_t im} void { + // TODO: Free the JPEG. + // ckfree(im.data); + } $cc compile namespace export * @@ -116,9 +168,33 @@ When when /p/ has camera slice /slice/ /lambda/ with environment /e/ { # Display a camera slice When /someone/ wishes /p/ displays camera slice /slice/ & /p/ has region /r/ { - set origin [lindex $r 0 0] + set center [region centroid $r] # set scale [expr {$Display::WIDTH / $Camera::WIDTH}] # Use 1x scale instead of $scale so the projected tag doesn't redetect. # TODO: Mask the tag out? - Wish display runs [list Display::image {*}$origin $slice 1] + Wish display runs [list Display::image {*}$center $slice 0 1] +} + +When /someone/ wishes /p/ displays image /im/ { + # TODO: Download im if it starts with https?:// + # Load im if it ends in .jpg or .png or .jpeg + if {[string match "*.jpg" $im] || + [string match "*.jpeg" $im] || + [string match "*.png" $im]} { + # TODO: Support .png + set path [expr {[file pathtype $im] eq "relative" ? + "$::env(HOME)/folk-images/$im" : + $im}] + set im [image loadJpeg $path] + } + When $p has region /r/ { + # Compute a scale for im that will fit in the region width/height + # Draw im with scale and rotation + set center [region centroid $r] + Wish display runs [list Display::image {*}$center $im 0 1] + } + On unmatch { + # HACK: Leaves time for the display to finish trying to display this. + after 5000 [list image freeJpeg $im] + } } -- cgit v1.2.3 From 0a02f43f28cbedd7143e0855935e41f163fe9105 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 16 Aug 2023 14:26:15 -0400 Subject: Image display rotation. WIP: Breaks camera slice --- pi/Display.tcl | 6 ------ pi/rotate.tcl | 14 +++++++------- virtual-programs/images.folk | 2 +- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pi/Display.tcl b/pi/Display.tcl index 660056e1..56f9cbf0 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -380,12 +380,6 @@ dc proc drawImageTransparent {int x0 int y0 image_t image int transparentTone in } source "pi/rotate.tcl" dc proc drawImage {int x0 int y0 image_t image double radians int scale} void { - - drawImageTransparent(x0 - image.width*scale/2, - y0 - image.height*scale/2, - image, 0x00, scale); - return; - double radiansNormalized = fmod(radians, 2.0 * M_PI); if (radiansNormalized > M_PI) { radiansNormalized -= 2.0 * M_PI; diff --git a/pi/rotate.tcl b/pi/rotate.tcl index 028ffc35..8a196471 100644 --- a/pi/rotate.tcl +++ b/pi/rotate.tcl @@ -30,9 +30,9 @@ dc proc shearY {image_t sprite int x0 int y0 int width int height double sy} voi int shear = sy * (x - x0); int from = y*sprite.bytesPerRow + x*sprite.components; int to = (y + shear)*sprite.bytesPerRow + x*sprite.components; - sprite.data[to] = sprite.data[from]; + memmove(&sprite.data[to], &sprite.data[from], sprite.components); // Blot out the unsheared part - if (from != to) { sprite.data[from] = 0x00; } + if (from != to) { memset(&sprite.data[from], 0x00, sprite.components); } } } } else if (sy < 0) { @@ -41,9 +41,9 @@ dc proc shearY {image_t sprite int x0 int y0 int width int height double sy} voi int shear = sy * (x - x0); // Is negative. int from = y*sprite.bytesPerRow + x*sprite.components; int to = (y + shear)*sprite.bytesPerRow + x*sprite.components; - sprite.data[to] = sprite.data[from]; + memmove(&sprite.data[to], &sprite.data[from], sprite.components); // Blot out the unsheared part - if (from != to) { sprite.data[from] = 0x00; } + if (from != to) { memset(&sprite.data[from], 0x00, sprite.components); } } } } @@ -56,9 +56,9 @@ dc proc rotate180 {image_t sprite int x0 int y0 int width int height} void { int icenter = imin + (imax - imin)/2; for (int i = imin; i < icenter; i += sprite.components) { int j = imax - (i - imin); - uint8_t temp = sprite.data[i]; - sprite.data[i] = sprite.data[j]; - sprite.data[j] = temp; + uint8_t temp[sprite.components]; memcpy(temp, &sprite.data[i], sprite.components); + memmove(&sprite.data[i], &sprite.data[j], sprite.components); + memcpy(&sprite.data[j], temp, sprite.components); } } dc proc rotateMakeImage {int width int height int components double radians diff --git a/virtual-programs/images.folk b/virtual-programs/images.folk index d98c58d0..09359ac6 100644 --- a/virtual-programs/images.folk +++ b/virtual-programs/images.folk @@ -191,7 +191,7 @@ When /someone/ wishes /p/ displays image /im/ { # Compute a scale for im that will fit in the region width/height # Draw im with scale and rotation set center [region centroid $r] - Wish display runs [list Display::image {*}$center $im 0 1] + Wish display runs [list Display::image {*}$center $im [region angle $r] 1] } On unmatch { # HACK: Leaves time for the display to finish trying to display this. -- cgit v1.2.3 From 43cd75eff9be56a6ca4e6286b9031ed4670be391 Mon Sep 17 00:00:00 2001 From: Charles Chamberlain Date: Wed, 16 Aug 2023 14:45:33 -0400 Subject: Add bottomright, topleft, region functions etc --- lib/math.tcl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/math.tcl b/lib/math.tcl index 9fc6839a..f688a465 100644 --- a/lib/math.tcl +++ b/lib/math.tcl @@ -139,6 +139,18 @@ namespace eval ::region { set bottomEdgeIndex [lindex [lsort -indices -real -index 1 $edgeMidpoints] end] vec2 midpoint {*}[edgeToLineSegment $r [lindex [edges $r] $bottomEdgeIndex]] } + proc bottomleft {r} { + lindex [vertices $r] 0 + } + proc bottomright {r} { + lindex [vertices $r] 1 + } + proc topright {r} { + lindex [vertices $r] 2 + } + proc topleft {r} { + lindex [vertices $r] 3 + } proc mapVertices {varname r body} { lreplace $r 0 0 [uplevel [list lmap $varname [vertices $r] $body]] -- cgit v1.2.3 From b774f27f5d3b6b4b1376f586e92ee04f93b65cd4 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 16 Aug 2023 16:32:12 -0400 Subject: Fix rotation of camera slices (don't use stride) --- pi/Display.tcl | 6 ++++-- virtual-programs/images.folk | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pi/Display.tcl b/pi/Display.tcl index 56f9cbf0..cf7a994d 100644 --- a/pi/Display.tcl +++ b/pi/Display.tcl @@ -390,12 +390,14 @@ dc proc drawImage {int x0 int y0 image_t image double radians int scale} void { image_t temp = rotateMakeImage(image.width, image.height, image.components, radiansNormalized, &imageX, &imageY); + // Draw the image into the temp image. for (int y = 0; y < image.height; y++) { - memcpy(&temp.data[(y + imageY) * temp.bytesPerRow + imageX * image.components], + memcpy(&temp.data[(y + imageY) * temp.bytesPerRow + imageX * temp.components], &image.data[y * image.bytesPerRow], - image.bytesPerRow); + image.width * image.components); } + rotate(temp, imageX, imageY, image.width, image.height, radiansNormalized); // Find corners of rotated rectangle diff --git a/virtual-programs/images.folk b/virtual-programs/images.folk index 09359ac6..3d5147d1 100644 --- a/virtual-programs/images.folk +++ b/virtual-programs/images.folk @@ -172,7 +172,7 @@ When /someone/ wishes /p/ displays camera slice /slice/ & /p/ has region /r/ { # set scale [expr {$Display::WIDTH / $Camera::WIDTH}] # Use 1x scale instead of $scale so the projected tag doesn't redetect. # TODO: Mask the tag out? - Wish display runs [list Display::image {*}$center $slice 0 1] + Wish display runs [list Display::image {*}$center $slice 0 1] } When /someone/ wishes /p/ displays image /im/ { -- cgit v1.2.3 From 5c1e806009788e2fa168a63598fed549fc9550ce Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 16 Aug 2023 17:43:50 -0400 Subject: Support getting images from internet, caching them --- virtual-programs/images.folk | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/virtual-programs/images.folk b/virtual-programs/images.folk index 3d5147d1..72f62143 100644 --- a/virtual-programs/images.folk +++ b/virtual-programs/images.folk @@ -175,26 +175,40 @@ When /someone/ wishes /p/ displays camera slice /slice/ & /p/ has region /r/ { Wish display runs [list Display::image {*}$center $slice 0 1] } +set ::imagesCache [dict create] When /someone/ wishes /p/ displays image /im/ { - # TODO: Download im if it starts with https?:// - # Load im if it ends in .jpg or .png or .jpeg - if {[string match "*.jpg" $im] || - [string match "*.jpeg" $im] || - [string match "*.png" $im]} { - # TODO: Support .png - set path [expr {[file pathtype $im] eq "relative" ? - "$::env(HOME)/folk-images/$im" : - $im}] - set im [image loadJpeg $path] + if {[dict exists $::imagesCache $im]} { + set im [dict get $::imagesCache $im] + } else { + set impath $im + if {[string match "http*://*" $impath]} { + set im /tmp/[regsub -all {\W+} $impath "_"] + exec -ignorestderr curl -o$im $impath + } + if {[string match "*jpg" $im] || + [string match "*jpeg" $im] || + [string match "*png" $im]} { + # TODO: Support .png + set path [expr {[file pathtype $im] eq "relative" ? + "$::env(HOME)/folk-images/$im" : + $im}] + set im [image loadJpeg $path] + dict set ::imagesCache $impath $im + } } When $p has region /r/ { # Compute a scale for im that will fit in the region width/height # Draw im with scale and rotation set center [region centroid $r] + # set width [region width $r] + # set height [region height $r] + # set scale [expr {min($width / [image width $im], + # $height / [image height $im])}] + # Wish $p is labelled $im Wish display runs [list Display::image {*}$center $im [region angle $r] 1] } - On unmatch { - # HACK: Leaves time for the display to finish trying to display this. - after 5000 [list image freeJpeg $im] - } + # On unmatch { + # # HACK: Leaves time for the display to finish trying to display this. + # after 5000 [list image freeJpeg $im] + # } } -- cgit v1.2.3 From e0cc89c5fc5f2423ba87a69b900b939eb06c8c66 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 16 Aug 2023 18:10:46 -0400 Subject: Fix region move distance bug --- lib/math.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/math.tcl b/lib/math.tcl index f688a465..23efc57b 100644 --- a/lib/math.tcl +++ b/lib/math.tcl @@ -274,7 +274,7 @@ namespace eval ::region { error "region move: Invalid distance $distance" } if {$unit eq "%"} { - set distance [* distance 0.01] + set distance [* $distance 0.01] set unit "" } if {$unit eq ""} { -- cgit v1.2.3 From 3e010714d558fbed21647410c1372ff0920d230a Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 17 Aug 2023 11:28:39 -0400 Subject: Catch kill --- main.tcl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.tcl b/main.tcl index 51d87f3f..9a7a4d1a 100644 --- a/main.tcl +++ b/main.tcl @@ -2,7 +2,11 @@ if {$tcl_version eq 8.5} { error "Don't use Tcl 8.5 / macOS system Tcl. Quitting # TODO: Fix this hack. set thisPid [pid] -foreach pid [exec pgrep tclsh8.6] { if {$pid ne $thisPid} { exec kill -9 $pid } } +foreach pid [try { exec pgrep tclsh8.6 } on error e { list }] { + if {$pid ne $thisPid} { + exec kill -9 $pid + } +} exec sleep 1 if {[info exists ::argv0] && $::argv0 eq [info script]} { -- cgit v1.2.3 From 4a8ac7cad8176d3b508c9384408898b351c60f03 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 17 Aug 2023 11:52:46 -0400 Subject: Skip already-existing files --- virtual-programs/print.folk | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/virtual-programs/print.folk b/virtual-programs/print.folk index badd191f..44a7d433 100644 --- a/virtual-programs/print.folk +++ b/virtual-programs/print.folk @@ -163,6 +163,10 @@ proc nextId {} { set id 0 } + while {[file exists "$::env(HOME)/folk-printed-programs/$id.folk"]} { + incr id + } + set fp [open "$::env(HOME)/folk-printed-programs/next-id.txt" w] puts $fp [expr {$id + 1}] close $fp -- cgit v1.2.3 From 4fd01f6e8eacc3b63651efe3cd8e251d275f2fc9 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 17 Aug 2023 12:38:07 -0400 Subject: Factor out image load --- virtual-programs/images.folk | 48 ++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/virtual-programs/images.folk b/virtual-programs/images.folk index 72f62143..c8e53951 100644 --- a/virtual-programs/images.folk +++ b/virtual-programs/images.folk @@ -136,6 +136,33 @@ namespace eval ::image { } $cc compile + variable imagesCache [dict create] + # Loads a URL or file path if passed. If passed a valid image_t, + # just returns that image_t. + proc load {im} { + variable imagesCache + if {[dict exists $imagesCache $im]} { + set im [dict get $imagesCache $im] + } else { + set impath $im + if {[string match "http*://*" $impath]} { + set im /tmp/[regsub -all {\W+} $impath "_"] + exec -ignorestderr curl -o$im $impath + } + if {[string match "*jpg" $im] || + [string match "*jpeg" $im] || + [string match "*png" $im]} { + # TODO: Support .png + set path [expr {[file pathtype $im] eq "relative" ? + "$::env(HOME)/folk-images/$im" : + $im}] + set im [image loadJpeg $path] + dict set imagesCache $impath $im + } + } + set im + } + namespace export * namespace ensemble create } @@ -175,27 +202,8 @@ When /someone/ wishes /p/ displays camera slice /slice/ & /p/ has region /r/ { Wish display runs [list Display::image {*}$center $slice 0 1] } -set ::imagesCache [dict create] When /someone/ wishes /p/ displays image /im/ { - if {[dict exists $::imagesCache $im]} { - set im [dict get $::imagesCache $im] - } else { - set impath $im - if {[string match "http*://*" $impath]} { - set im /tmp/[regsub -all {\W+} $impath "_"] - exec -ignorestderr curl -o$im $impath - } - if {[string match "*jpg" $im] || - [string match "*jpeg" $im] || - [string match "*png" $im]} { - # TODO: Support .png - set path [expr {[file pathtype $im] eq "relative" ? - "$::env(HOME)/folk-images/$im" : - $im}] - set im [image loadJpeg $path] - dict set ::imagesCache $impath $im - } - } + set im [image load $im] When $p has region /r/ { # Compute a scale for im that will fit in the region width/height # Draw im with scale and rotation -- cgit v1.2.3 From 277237a6f5c1601a87c26be17203275ba43a0d81 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 17 Aug 2023 13:03:26 -0400 Subject: Add running programs page to web --- web.tcl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/web.tcl b/web.tcl index 220d3337..aa7587d4 100644 --- a/web.tcl +++ b/web.tcl @@ -50,6 +50,7 @@ proc handlePage {path contentTypeVar} {