summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOmar Rizwan <omar@omar.website>2023-06-30 18:11:12 +0000
committerGitHub <noreply@github.com>2023-06-30 18:11:12 +0000
commit8a8a749eb2afbf6b0636c280554c32219fff1dd2 (patch)
treea4fca4989a76d0e17c2d17ffe8ad1ad349111df2
parentLeft-align labels, watch for .folk.temp files (diff)
parentFix load librt to work on folk0 also (diff)
downloadfolk-8a8a749eb2afbf6b0636c280554c32219fff1dd2.tar.gz
folk-8a8a749eb2afbf6b0636c280554c32219fff1dd2.zip
Merge pull request #42 from FolkComputer/osnr/camera-pipeline
Run Camera in subprocess
-rw-r--r--Makefile2
-rw-r--r--README.md9
-rw-r--r--hosts.tcl49
-rw-r--r--laptop.tcl76
-rw-r--r--lib/c.tcl37
-rw-r--r--lib/environment.tcl24
-rw-r--r--lib/evaluator.tcl73
-rw-r--r--lib/language.tcl67
-rw-r--r--lib/peer.tcl56
-rw-r--r--lib/process.tcl128
-rw-r--r--main.tcl144
-rw-r--r--pi/AprilTags.tcl63
-rw-r--r--pi/Camera.tcl525
-rw-r--r--pi/pi.tcl47
-rw-r--r--play/fork-play.tcl23
-rw-r--r--play/zygote-play.tcl60
-rw-r--r--test/basic.tcl8
-rw-r--r--test/commit.tcl7
-rw-r--r--test/cstructs.tcl23
-rw-r--r--test/joins.tcl7
-rw-r--r--test/negation.tcl6
-rw-r--r--test/perf.tcl12
-rw-r--r--test/process.tcl59
-rw-r--r--test/shm.tcl58
-rw-r--r--test/trie.tcl7
-rw-r--r--test/with-all.tcl11
-rw-r--r--user-programs/haippi7/laser-pi.tcl2
-rw-r--r--virtual-programs/camera.folk37
-rw-r--r--virtual-programs/new-program-web-editor.folk9
-rw-r--r--virtual-programs/regions.folk3
-rw-r--r--virtual-programs/shapes.folk2
-rw-r--r--virtual-programs/tags-and-calibration.folk5
-rw-r--r--virtual-programs/web-editor.folk188
-rw-r--r--web.tcl26
34 files changed, 1116 insertions, 737 deletions
diff --git a/Makefile b/Makefile
index fe17ea2d..d83bfceb 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ ssh:
ssh folk@$(FOLK_SHARE_NODE)
flamegraph:
- sudo perf record -F 997 --tid=$(shell pgrep tclsh8.6) -g -- sleep 30
+ sudo perf record -F 997 --tid=$(shell pgrep tclsh8.6 | head -1) -g -- sleep 30
sudo perf script -f > out.perf
~/FlameGraph/stackcollapse-perf.pl out.perf > out.folded
~/FlameGraph/flamegraph.pl out.folded > out.svg
diff --git a/README.md b/README.md
index 0d539e9e..06d7575f 100644
--- a/README.md
+++ b/README.md
@@ -194,17 +194,10 @@ Edit /boot/cmdline.txt https://github.com/raspberrypi/firmware/issues/1647#issue
https://askubuntu.com/questions/1321443/very-long-startup-time-on-ubuntu-server-network-configuration
(add `optional: true` to all netplan interfaces)
-## Setup notes
-
-- get a separate computer (Raspberry Pi 4, probably). don't use your laptop.
-- make as solid / permanent a mount as you can. you shouldn't be
- scared of it falling and you shouldn't have to take it apart and put
- it back together every time
-
## License
We intend to release this repo as open-source under an MIT, GPLv3,
-Apache 2.0, or AGPLv3 license by June 2023 or earlier; by contributing
+Apache 2.0, or AGPLv3 license in 2023; by contributing
code, you're also agreeing to license your code under whichever
license we end up choosing.
diff --git a/hosts.tcl b/hosts.tcl
index 18675971..d5b2e795 100644
--- a/hosts.tcl
+++ b/hosts.tcl
@@ -1,25 +1,36 @@
-set wifi "Fios-LGTS3-5G"
-catch {
- if {$::tcl_platform(os) eq "Darwin"} {
- set wifi [exec sh -c {/Sy*/L*/Priv*/Apple8*/V*/C*/R*/airport -I | sed -n "s/^.*SSID: \(.*\)$/\1/p"}]
- } elseif {$::tcl_platform(os) eq "Linux"} {
- set wifi [exec iwgetid -r]
- }
-}
-
-if {$wifi eq "cynosure"} { set ::shareNode "folk-omar.local" } \
-elseif {$wifi eq "Verizon_TWRHB4"} { set ::shareNode "folk-cwervo.local" } \
-elseif {$wifi eq "WONDERLAND"} { set ::shareNode "folk-haip.local" } \
-elseif {$wifi eq "GETNEAR"} { set ::shareNode "folk-ian.local" } \
-elseif {$wifi eq "Fios-LGTS3-5G" || $wifi eq "Fios-LGTS3"} { set ::shareNode "folk0.local" } \
-elseif {[string match "_onefact.org*" $wifi]} { set ::shareNode "folk-onefact.local" } \
-else { set ::shareNode "folk0.local" }
-
if {[info exists ::env(FOLK_SHARE_NODE)]} {
set ::shareNode $::env(FOLK_SHARE_NODE)
+} else {
+ try {
+ if {$::tcl_platform(os) eq "Darwin"} {
+ set wifi [exec sh -c {/Sy*/L*/Priv*/Apple8*/V*/C*/R*/airport -I | sed -n "s/^.*SSID: \(.*\)$/\1/p"}]
+ } elseif {$::tcl_platform(os) eq "Linux"} {
+ set wifi [exec iwgetid -r]
+ }
+
+ if {$wifi eq "cynosure"} {
+ set ::shareNode "folk-omar.local"
+ } elseif {$wifi eq "Verizon_TWRHB4"} {
+ set ::shareNode "folk-cwervo.local"
+ } elseif {$wifi eq "WONDERLAND"} {
+ set ::shareNode "folk-haip.local"
+ } elseif {$wifi eq "GETNEAR"} {
+ set ::shareNode "folk-ian.local"
+ } elseif {$wifi eq "Fios-LGTS3-5G" || $wifi eq "Fios-LGTS3"} {
+ set ::shareNode "folk0.local"
+ } elseif {[string match "_onefact.org*" $wifi]} {
+ set ::shareNode "folk-onefact.local"
+ } else {
+ # there's no default.
+ }
+ } on error e {
+ set ::shareNode "none"
+ }
}
-if {$::shareNode eq "none"} { unset ::shareNode }
+
+if {[info exists ::shareNode] && $::shareNode eq "none"} { unset ::shareNode }
if {[info exists ::argv] && $::argv eq "shareNode"} {
- puts $::shareNode
+ if {[info exists ::shareNode]} { puts $::shareNode } \
+ else { puts none }
}
diff --git a/laptop.tcl b/laptop.tcl
index 6154159a..c3aeba1a 100644
--- a/laptop.tcl
+++ b/laptop.tcl
@@ -1,5 +1,3 @@
-package require Tk
-
namespace eval Display {
variable WIDTH 800
variable HEIGHT 600
@@ -9,12 +7,37 @@ namespace eval Display {
variable green green
variable red red
- canvas .display -background black -width $Display::WIDTH -height $Display::HEIGHT
- pack .display
- wm title . $::nodename
- wm geometry . [set Display::WIDTH]x[expr {$Display::HEIGHT + 40}]-0+0 ;# align to top-right of screen
-
- proc init {} {}
+ proc init {} {
+ package require Tk
+
+ canvas .display -background black -width $Display::WIDTH -height $Display::HEIGHT
+ pack .display
+ wm title . $::thisProcess
+ wm geometry . [set Display::WIDTH]x[expr {$Display::HEIGHT + 40}]-0+0 ;# align to top-right of screen
+
+ set ::chs [list]
+ bind . <KeyPress> {apply {{k} {
+ lappend ::chs $k
+ Retract keyboard claims the keyboard character log is /something/
+ Assert keyboard claims the keyboard character log is $::chs
+ Step
+ }} %K}
+
+ proc ::Display::commit {} {
+ .display delete all
+
+ set displayList [list]
+ foreach match [Statements::findMatches {/someone/ wishes display runs /command/}] {
+ lappend displayList [dict get $match command]
+ }
+
+ proc lcomp {a b} {expr {[lindex $a 2] == "text"}}
+ variable displayTime
+ set displayTime [time {
+ eval [join [lsort -command lcomp $displayList] "\n"]
+ }]
+ }
+ }
proc fillRect {x0 y0 x1 y1 color} {
uplevel [list Wish display runs [list .display create rectangle $x0 $y0 $x1 $y1 -fill $color]]
@@ -30,37 +53,18 @@ namespace eval Display {
}
variable displayTime
- proc commit {} {
- .display delete all
-
- set displayList [list]
- foreach match [Statements::findMatches {/someone/ wishes display runs /command/}] {
- lappend displayList [dict get $match command]
- }
- proc lcomp {a b} {expr {[lindex $a 2] == "text"}}
- variable displayTime
- set displayTime [time {
- eval [join [lsort -command lcomp $displayList] "\n"]
- }]
- }
+ # No-op until Display::init is called.
+ proc commit {} {}
}
-set ::chs [list]
-proc handleKeyPress {k} {
- lappend ::chs $k
- Retract keyboard claims the keyboard character log is /something/
- Assert keyboard claims the keyboard character log is $::chs
- Step
-}
-bind . <KeyPress> {handleKeyPress %K}
-
Assert when /program/ has error /err/ with info /info/ {{program err info} {
puts stderr "Error: $program has error $err with info $info"
}}
source "hosts.tcl"
if {[info exists ::shareNode]} {
+ puts "Will try to share with: $::shareNode"
# copy to Pi
if {[catch {
# TODO: forward entry point
@@ -69,18 +73,22 @@ if {[info exists ::shareNode]} {
exec -ignorestderr ssh folk@$::shareNode -- sudo systemctl restart folk >@stdout &
} err]} {
puts "error syncing: $err"
- puts "Proceeding without sharing to table."
+ puts "Proceeding without sharing."
} else {
source "lib/peer.tcl"
peer $::shareNode
- Assert "laptop.tcl" wishes $::nodename shares statements like \
- [list $::nodename is providing root virtual programs /rootVirtualPrograms/]
+ Assert "laptop.tcl" wishes $::thisProcess shares statements like \
+ [list $::thisProcess is providing root virtual programs /rootVirtualPrograms/]
}
}
-Display::init
+try {
+ Display::init
+} on error e {
+ puts stderr "Failed to init display: $e"
+}
loadVirtualPrograms
Step
diff --git a/lib/c.tcl b/lib/c.tcl
index b09c5bb9..503e0b9f 100644
--- a/lib/c.tcl
+++ b/lib/c.tcl
@@ -43,6 +43,9 @@ namespace eval c {
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
+
+ #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) { return TCL_ERROR; }
}
variable code [list]
variable objtypes [list]
@@ -64,30 +67,37 @@ namespace eval c {
}
variable argtypes {
- int { expr {{ int $argname; Tcl_GetIntFromObj(interp, $obj, &$argname); }}}
- bool { expr {{ int $argname; Tcl_GetIntFromObj(interp, $obj, &$argname); }}}
- int32_t { expr {{ int $argname; Tcl_GetIntFromObj(interp, $obj, &$argname); }}}
- char { expr {{ char $argname = Tcl_GetString($obj)[0]; }}}
- size_t { expr {{ size_t $argname; Tcl_GetLongFromObj(interp, $obj, (long *)&$argname); }}}
- intptr_t { expr {{ intptr_t $argname; Tcl_GetLongFromObj(interp, $obj, (long *)&$argname); }}}
- uint16_t { expr {{ uint16_t $argname; Tcl_GetIntFromObj(interp, $obj, (int *)&$argname); }}}
- uint32_t { expr {{ uint32_t $argname; sscanf(Tcl_GetString($obj), "%"PRIu32, &$argname); }}}
- uint64_t { expr {{ uint64_t $argname; sscanf(Tcl_GetString($obj), "%"PRIu64, &$argname); }}}
+ int { expr {{ int $argname; __ENSURE_OK(Tcl_GetIntFromObj(interp, $obj, &$argname)); }}}
+ double { expr {{ double $argname; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $obj, &$argname)); }}}
+ bool { expr {{ int $argname; __ENSURE_OK(Tcl_GetIntFromObj(interp, $obj, &$argname)); }}}
+ int32_t { expr {{ int $argname; __ENSURE_OK(Tcl_GetIntFromObj(interp, $obj, &$argname)); }}}
+ char { expr {{
+ char $argname;
+ {
+ int _len_$argname;
+ char* _tmp_$argname = Tcl_GetStringFromObj($obj, &_len_$argname);
+ __ENSURE(_len_$argname >= 1);
+ $argname = _tmp_$argname[0];
+ }
+ }}}
+ size_t { expr {{ size_t $argname; __ENSURE_OK(Tcl_GetLongFromObj(interp, $obj, (long *)&$argname)); }}}
+ intptr_t { expr {{ intptr_t $argname; __ENSURE_OK(Tcl_GetLongFromObj(interp, $obj, (long *)&$argname)); }}}
+ uint16_t { expr {{ uint16_t $argname; __ENSURE_OK(Tcl_GetIntFromObj(interp, $obj, (int *)&$argname)); }}}
+ uint32_t { expr {{ uint32_t $argname; __ENSURE(sscanf(Tcl_GetString($obj), "%"PRIu32, &$argname) == 1); }}}
+ uint64_t { expr {{ uint64_t $argname; __ENSURE(sscanf(Tcl_GetString($obj), "%"PRIu64, &$argname) == 1); }}}
char* { expr {{ char* $argname = Tcl_GetString($obj); }} }
Tcl_Obj* { expr {{ Tcl_Obj* $argname = $obj; }}}
default {
if {[string index $argtype end] == "*"} {
expr {{
$argtype $argname;
- if (sscanf(Tcl_GetString($obj), "($argtype) 0x%p", &$argname) != 1) {
- return TCL_ERROR;
- }
+ __ENSURE(sscanf(Tcl_GetString($obj), "($argtype) 0x%p", &$argname) == 1);
}}
} elseif {[regexp {([^\[]+)\[(\d*)\]$} $argtype -> basetype arraylen]} {
# note: arraylen can be ""
expr {{
int ${argname}_objc; Tcl_Obj** ${argname}_objv;
- Tcl_ListObjGetElements(interp, $obj, &${argname}_objc, &${argname}_objv);
+ __ENSURE_OK(Tcl_ListObjGetElements(interp, $obj, &${argname}_objc, &${argname}_objv));
$basetype $argname\[${argname}_objc\];
{
for (int i = 0; i < ${argname}_objc; i++) {
@@ -113,6 +123,7 @@ namespace eval c {
variable rtypes {
int { expr {{ $robj = Tcl_NewIntObj($rvalue); }}}
int32_t { expr {{ $robj = Tcl_NewIntObj($rvalue); }}}
+ double { expr {{ $robj = Tcl_NewDoubleObj($rvalue); }}}
char { expr {{ $robj = Tcl_ObjPrintf("%c", $rvalue); }}}
bool { expr {{ $robj = Tcl_NewIntObj($rvalue); }}}
uint16_t { expr {{ $robj = Tcl_NewIntObj($rvalue); }}}
diff --git a/lib/environment.tcl b/lib/environment.tcl
index ae5a369b..28dd33fb 100644
--- a/lib/environment.tcl
+++ b/lib/environment.tcl
@@ -1,5 +1,3 @@
-namespace eval ::SerializableEnvironment {}
-
proc serializeEnvironment {} {
set argnames [list]
set argvalues [list]
@@ -10,31 +8,9 @@ proc serializeEnvironment {} {
lappend argvalues [uplevel [list set $name]]
}
}
- # foreach importName [namespace eval ::SerializableEnvironment {namespace import}] {
- # dict set env %$importName [namespace origin ::SerializableEnvironment::$importName]
- # }
- # foreach procName [info procs ::SerializableEnvironment::*] {
- # if {![dict exists $env %[namespace tail $procName]]} {
- # dict set env ^[namespace tail $procName] \
- # [list [info args $procName] [info body $procName]]
- # }
- # }
list $argnames $argvalues
}
-# proc deserializeEnvironment {env} {
-# dict for {name value} $env {
-# if {[string index $name 0] eq "^"} {
-# proc ::SerializableEnvironment::[string range $name 1 end] {*}$value
-# } elseif {[string index $name 0] eq "%"} {
-# namespace eval ::SerializableEnvironment \
-# [list namespace import -force $value]
-# } else {
-# set ::SerializableEnvironment::$name $value
-# }
-# }
-# }
-
set ::Evaluator::totalTimesMap [dict create]
set ::Evaluator::runsMap [dict create]
diff --git a/lib/evaluator.tcl b/lib/evaluator.tcl
index 8eacc659..6d462d09 100644
--- a/lib/evaluator.tcl
+++ b/lib/evaluator.tcl
@@ -6,8 +6,14 @@ namespace eval statement {
$cc include <stdlib.h>
$cc include <assert.h>
- $cc struct statement_handle_t { int32_t idx; int32_t gen; }
- $cc struct match_handle_t { int32_t idx; int32_t gen; }
+ $cc code {
+ typedef struct statement_handle_t { int32_t idx; int32_t gen; } statement_handle_t;
+ typedef struct match_handle_t { int32_t idx; int32_t gen; } match_handle_t;
+ }
+ $cc rtype statement_handle_t { $robj = Tcl_ObjPrintf("s%d:%d", $rvalue.idx, $rvalue.gen); }
+ $cc argtype statement_handle_t { statement_handle_t $argname; sscanf(Tcl_GetString($obj), "s%d:%d", &$argname.idx, &$argname.gen); }
+ $cc rtype match_handle_t { $robj = Tcl_ObjPrintf("m%d:%d", $rvalue.idx, $rvalue.gen); }
+ $cc argtype match_handle_t { match_handle_t $argname; sscanf(Tcl_GetString($obj), "m%d:%d", &$argname.idx, &$argname.gen); }
$cc enum edge_type_t { EMPTY, PARENT, CHILD }
@@ -562,7 +568,7 @@ namespace eval Statements { ;# singleton Statement store
for (int i = 0; i < resultsCount; i++) {
Tcl_Obj* matchObj = environmentToTclDict(results[i]);
statement_handle_t id = results[i]->matchedStatementIds[0];
- Tcl_DictObjPut(NULL, matchObj, Tcl_ObjPrintf("__matcheeIds"), Tcl_ObjPrintf("{idx %d gen %d}", id.idx, id.gen));
+ Tcl_DictObjPut(NULL, matchObj, Tcl_ObjPrintf("__matcheeIds"), Tcl_ObjPrintf("{s%d:%d}", id.idx, id.gen));
Tcl_ListObjAppendElement(NULL, ret, matchObj);
ckfree((char *)results[i]);
}
@@ -666,7 +672,7 @@ namespace eval Statements { ;# singleton Statement store
s->typePtr = &statement_t_ObjType;
s->internalRep.otherValuePtr = &statements[i];
- Tcl_ListObjAppendElement(NULL, ret, Tcl_ObjPrintf("idx %d", i));
+ Tcl_ListObjAppendElement(NULL, ret, Tcl_ObjPrintf("s%d:%d", i, statements[i].gen));
Tcl_ListObjAppendElement(NULL, ret, s);
}
return ret;
@@ -674,9 +680,7 @@ namespace eval Statements { ;# singleton Statement store
proc dot {} {
set dot [list]
dict for {id stmt} [all] {
- set id [dict get $id idx]
-
- lappend dot "subgraph cluster_$id {"
+ lappend dot "subgraph <cluster_$id> {"
lappend dot "color=lightgray;"
set label [statement clause $stmt]
@@ -684,23 +688,20 @@ namespace eval Statements { ;# singleton Statement store
expr { [string length $line] > 80 ? "[string range $line 0 80]..." : $line }
}] "\n"]
set label [string map {"\"" "\\\""} [string map {"\\" "\\\\"} $label]]
- lappend dot "s$id \[label=\"s$id: $label\"\];"
+ lappend dot "<$id> \[label=\"$id: $label\"\];"
- dict for {matchId_ _} [statement parentMatchIds $stmt] {
- set matchId [dict get $matchId_ idx]
- if {$matchId == -1} continue
- set parents [lmap edge [matchEdges $matchId_] {expr {
- [dict get $edge type] == 1 ? "s[dict get $edge statement idx]" : [continue]
+ dict for {matchId _} [statement parentMatchIds $stmt] {
+ set parents [lmap edge [matchEdges $matchId] {expr {
+ [dict get $edge type] == 1 ? "[dict get $edge statement]" : [continue]
}}]
- lappend dot "m$matchId \[label=\"m$matchId <- $parents\"\];"
- lappend dot "m$matchId -> s$id;"
+ lappend dot "<$matchId> \[label=\"$matchId <- $parents\"\];"
+ lappend dot "<$matchId> -> <$id>;"
}
lappend dot "}"
- dict for {childId _} [statement childMatchIds $stmt] {
- set childId [dict get $childId idx]
- lappend dot "s$id -> m$childId;"
+ dict for {childMatchId _} [statement childMatchIds $stmt] {
+ lappend dot "<$id> -> <$childMatchId>;"
}
}
return "digraph { rankdir=LR; [join $dot "\n"] }"
@@ -826,7 +827,7 @@ namespace eval Evaluator {
Tcl_ListObjAppendElement(interp, env, result->bindings[i].value);
}
- Tcl_ObjSetVar2(interp, Tcl_ObjPrintf("::matchId"), NULL, Tcl_ObjPrintf("idx %d gen %d", matchId.idx, matchId.gen), 0);
+ Tcl_ObjSetVar2(interp, Tcl_ObjPrintf("::matchId"), NULL, Tcl_ObjPrintf("m%d:%d", matchId.idx, matchId.gen), 0);
tryRunInSerializedEnvironment(interp, lambda, env);
}
}
@@ -994,20 +995,6 @@ namespace eval Evaluator {
statement_t* collect = get(collectId);
- // First, delete the existing match child.
- {
- for (size_t i = 0; i < collect->n_edges; i++) {
- edge_to_match_t* edge = statementEdgeAt(collect, i);
- if (edge->type == CHILD) {
- match_handle_t childMatchId = edge->match;
- matchGet(childMatchId)->recollectOnDestruction = false;
- reactToMatchRemoval(interp, childMatchId);
- matchRemove(childMatchId);
- break;
- }
- }
- }
-
Tcl_Obj* clause = collect->clause;
int clauseLength; Tcl_Obj** clauseWords;
Tcl_ListObjGetElements(interp, clause, &clauseLength, &clauseWords);
@@ -1037,17 +1024,35 @@ namespace eval Evaluator {
for (int j = 0; j < results[i]->matchedStatementIdsCount; j++) {
parents[parentsCount++] = results[i]->matchedStatementIds[j];
}
+ ckfree((char *)results[i]);
}
+ // Create a new match for the new collection.
match_handle_t matchId = addMatchImpl(parentsCount, parents);
match_t* match = matchGet(matchId);
match->recollectOnDestruction = true;
match->recollectCollectId = collectId;
+ // Run the When body within this new match.
env = Tcl_DuplicateObj(env);
Tcl_ListObjAppendElement(NULL, env, matches);
- Tcl_ObjSetVar2(interp, Tcl_ObjPrintf("::matchId"), NULL, Tcl_ObjPrintf("idx %d gen %d", matchId.idx, matchId.gen), 0);
+ Tcl_ObjSetVar2(interp, Tcl_ObjPrintf("::matchId"), NULL, Tcl_ObjPrintf("m%d:%d", matchId.idx, matchId.gen), 0);
tryRunInSerializedEnvironment(interp, lambda, env);
+
+ // Finally, delete the old match child if any.
+ // (We do this last, _after_ adding the new match, because it helps with incrementality.)
+ {
+ for (size_t i = 0; i < collect->n_edges; i++) {
+ edge_to_match_t* edge = statementEdgeAt(collect, i);
+ if (edge->type == CHILD && !matchHandleIsEqual(edge->match, matchId)) {
+ match_handle_t childMatchId = edge->match;
+ matchGet(childMatchId)->recollectOnDestruction = false;
+ reactToMatchRemoval(interp, childMatchId);
+ matchRemove(childMatchId);
+ break;
+ }
+ }
+ }
}
$cc code {
diff --git a/lib/language.tcl b/lib/language.tcl
new file mode 100644
index 00000000..b706753a
--- /dev/null
+++ b/lib/language.tcl
@@ -0,0 +1,67 @@
+# 'Language' utilities that extend and customize base Tcl.
+
+proc fn {name argNames body} {
+ uplevel [list set ^$name [list $argNames $body]]
+}
+rename unknown _original_unknown
+# Trap resolution of commands so that they can call the lambda in
+# lexical scope created by `fn`.
+proc unknown {name args} {
+ if {[uplevel [list info exists ^$name]]} {
+ apply [uplevel [list set ^$name]] {*}$args
+ } else {
+ uplevel [list _original_unknown $name {*}$args]
+ }
+}
+
+# Trim indentation in multiline quoted text.
+proc undent {msg {whitespaceChars " "}} {
+ set msgLines [split $msg "\n"]
+ set maxLength [string length $msg]
+
+ set regExp [subst -nocommands {([$whitespaceChars]*)[^$whitespaceChars]}]
+
+ set indent [
+ tcl::mathfunc::min {*}[
+ lmap x $msgLines {
+ if {[regexp $regExp $x match whitespace]} {
+ string length $whitespace
+ } else {
+ lindex $maxLength
+ }
+ }
+ ]
+ ]
+
+ join [ltrim [lmap x $msgLines {string range $x $indent end}]] "\n"
+}
+# Remove empty items at the beginning and the end of a list.
+proc ltrim {list} {
+ set first [lsearch -not -exact $list {}]
+ set last [lsearch -not -exact [lreverse $list] {}]
+ return [
+ if {$first == -1} {
+ list
+ } else {
+ lrange $list $first end-$last
+ }
+ ]
+}
+
+proc python3 {args} {
+ exec python3 << [undent [join $args " "]]
+}
+
+proc assert condition {
+ set s "{$condition}"
+ if {![uplevel 1 expr $s]} {
+ set errmsg "assertion failed: $condition"
+ if {[lindex $condition 1] eq "eq" && [string index [lindex $condition 0] 0] eq "$"} {
+ set errmsg "$errmsg\n[uplevel 1 [list set [string range [lindex $condition 0] 1 end]]] is not equal to [lindex $condition 2]"
+ }
+ return -code error $errmsg
+ }
+}
+
+namespace import ::tcl::mathop::*
+namespace import ::tcl::mathfunc::*
diff --git a/lib/peer.tcl b/lib/peer.tcl
index 5570f9a0..49f7bb26 100644
--- a/lib/peer.tcl
+++ b/lib/peer.tcl
@@ -3,51 +3,72 @@ lappend auto_path "./vendor"
namespace eval clauseset {
# only used for statement syndication
- namespace export create add minus clauses
+ namespace export create add union difference clauses
proc create {args} {
set kvs [list]
foreach k $args { lappend kvs $k true }
dict create {*}$kvs
}
- proc add {sv k} { upvar $sv s; dict set s $k true }
- proc minus {s t} {
+ proc add {sv stmt} { upvar $sv s; dict set s $stmt true }
+
+ proc union {s t} { dict merge $s $t }
+ proc difference {s t} {
dict filter $s script {k v} {expr {![dict exists $t $k]}}
}
+
proc clauses {s} { dict keys $s }
namespace ensemble create
}
-namespace eval Peers {}
+namespace eval ::Peers {}
-proc ::peer {node} {
+proc ::peer {process} {
package require websocket
- namespace eval Peers::$node {
+ namespace eval ::Peers::$process {
variable connected false
variable prevShareStatements [clauseset create]
+ variable prevReceivedStatements [clauseset create]
proc log {s} {
- variable node
- puts "$::nodename -> $node: $s"
+ variable process
+ puts "$::thisProcess -> $process: $s"
}
proc setupSock {} {
- variable node
- log "Trying to connect to: ws://$node:4273/ws"
- variable sock [::websocket::open "ws://$node:4273/ws" [namespace code handleWs]]
+ variable process
+ log "Trying to connect to: ws://$process:4273/ws"
+ variable sock [::websocket::open "ws://$process:4273/ws" [namespace code handleWs]]
}
proc handleWs {sock 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 above)
+ run {
+ variable chan [uplevel {set chan}]
+ variable connected true
+ variable prevShareStatements [clauseset create]
+ variable prevReceivedStatements [clauseset create]
+ proc run {msg} {
+ variable chan
+ ::websocket::send $chan text $msg
+ }
+ }
} elseif {$type eq "disconnect"} {
log "Disconnected"
variable connected false
variable prevShareStatements [clauseset create]
+ variable prevReceivedStatements [clauseset create]
after 2000 [namespace code setupSock]
} elseif {$type eq "error"} {
log "WebSocket error: $type $msg"
after 2000 [namespace code setupSock]
- } elseif {$type eq "text" || $type eq "ping" || $type eq "pong"} {
- # We don't handle responses yet.
+ } elseif {$type eq "text"} {
+ eval $msg
+ } elseif {$type eq "ping" || $type eq "pong"} {
} else {
error "Unknown WebSocket event: $type $msg"
}
@@ -55,10 +76,13 @@ proc ::peer {node} {
proc run {msg} {
variable sock
- ::websocket::send $sock text $msg
+ ::websocket::send $sock text [list namespace eval ::Peers::$::thisProcess $msg]
}
- proc init {n} { variable node $n; setupSock }
+ proc init {n} {
+ variable process $n; setupSock
+ vwait ::Peers::${n}::connected
+ }
init
- } $node
+ } $process
}
diff --git a/lib/process.tcl b/lib/process.tcl
index 67642dfa..b3aa9bf4 100644
--- a/lib/process.tcl
+++ b/lib/process.tcl
@@ -1,15 +1,59 @@
-set ::processPrelude {
- source "main.tcl"
- proc every {ms body} {
- try $body
- after $ms [list after idle [namespace code [info level 0]]]
- }
+namespace eval ::Zygote {
+ set cc [c create]
+ $cc include <unistd.h>
+ $cc proc ::Zygote::fork {} int { return fork(); }
+ # FIXME: waitpid
+ # FIXME: some kind of shared-memory log queue
+ $cc compile
+
+ # The zygote is a process that's forked off during Folk
+ # startup. It can fork itself to create subprocesses on demand.
- Assert $::nodename wishes $::nodename shares all claims
+ # Fork Folk to create the zygote process (= set the current state
+ # of Folk as the startup state for all subprocesses that will be
+ # spawned later)
+ proc init {} {
+ variable reader
+ variable writer
+ lassign [chan pipe] reader writer
+ set pid [fork]
+ if {$pid == 0} {
+ # We're in the child (the zygote). We will block waiting
+ # for commands from the parent (the original/main thread).
+ close $writer
+ fconfigure $reader -buffering line
+ zygote
+
+ } else {
+ # We're still in the parent. The child (the zygote) is $pid.
+ close $reader
+ # We will send the zygote a message every time we want it to
+ # fork.
+ fconfigure $writer -buffering line
+ }
+ }
+ # Zygote's main loop.
+ proc zygote {} {
+ variable reader
+ set script ""
+ while {[gets $reader line] != -1} {
+ append script $line\n
+ if {[info complete $script]} {
+ set pid [fork]
+ if {$pid == 0} {
+ eval $script
+ exit 0
+ }
+ set script ""
+ }
+ }
+ exit 0
+ }
- source "lib/peer.tcl"
- peer "localhost"
- vwait Peers::localhost::connected
+ proc spawn {code} {
+ variable writer
+ puts $writer $code
+ }
}
proc On-process {name body} {
@@ -18,43 +62,37 @@ proc On-process {name body} {
set ::Processes::${name}::body $body
set ::Processes::${name}::this [uplevel {expr {[info exists this] ? $this : "<unknown>"}}]
namespace eval ::Processes::$name {
- variable tclfd [file tempfile tclfile tclfile.tcl]
- # TODO: send it the serialized environment
- set lambda [list {} $body]
- set run [list Evaluator::runInSerializedEnvironment $lambda [list]]
- puts $tclfd [join [list $::processPrelude $run] "\n"]; close $tclfd
-
- variable stdio [open "|tclsh8.6 $tclfile 2>@1" w+]
- variable pid [pid $stdio]
-
- variable log [list]
- proc handleReadable {} {
- variable name
- variable stdio
- variable log
- if {[gets $stdio line] >= 0} {
- lappend log $line
- puts "$name: $line"
- Retract process $name has standard output log /l/
- Assert process $name has standard output log $log
- Step
- } elseif {[eof $stdio]} { close $stdio }
- }
- fconfigure $stdio -blocking 0 -buffering line
- fileevent $stdio readable [namespace code handleReadable]
+ set processCode [list apply {{__name __body} {
+ set ::thisProcess $__name
- if {$this ne "<unknown>"} {
- Assert $this is running process $name
- }
+ Assert <lib/process.tcl> wishes $::thisProcess shares all wishes
+ Assert <lib/process.tcl> wishes $::thisProcess shares all claims
+
+ ::peer "localhost"
+
+ Assert <lib/process.tcl> claims $::thisProcess has pid [pid]
+ Assert when $::thisProcess has pid /something/ [list {} $__body]
+ Step
+ vwait forever
+ }} $name $body]
+
+ Zygote::spawn [list apply {{processCode} {
+ # A supervisor that wraps the subprocess.
+ set pid [Zygote::fork]
+ if {$pid == 0} {
+ eval $processCode
+ } else {
+ # TODO: Supervise the subprocess.
+ # waitpid $pid
+ # how to report outcomes to Folk?
+ # does it have an inbox? do we assert into Folk and let it retract?
+ }
+ }} $processCode]
- proc handleUnmatch {} {
- variable pid
- variable name
- catch {exec kill $pid}
- Retract /someone/ is running process $name
- Retract process $name has standard output log /something/
- namespace delete ::Processes::$name
+ When (non-capturing) $name has pid /pid/ {
+ On unmatch {
+ exec kill -9 $pid
+ }
}
- uplevel 2 [list On unmatch ::Processes::${name}::handleUnmatch]
}
}
diff --git a/main.tcl b/main.tcl
index 3b6faea7..e679a1ee 100644
--- a/main.tcl
+++ b/main.tcl
@@ -31,27 +31,17 @@ namespace eval Evaluator {
}
if {$this ne ""} {
Say $this has error $err with info $::errorInfo
- puts stderr "$::nodename: Error in $this, match $::matchId: $err\n$::errorInfo"
+ puts stderr "$::thisProcess: Error in $this, match $::matchId: $err\n$::errorInfo"
} else {
Say $::matchId has error $err with info $::errorInfo
- puts stderr "$::nodename: Error in match $::matchId: $err\n$::errorInfo"
+ puts stderr "$::thisProcess: Error in match $::matchId: $err\n$::errorInfo"
}
}
}
}
set ::logsize -1 ;# Hack to keep metrics working
-proc fn {name argNames body} {
- uplevel [list set ^$name [list $argNames $body]]
-}
-rename unknown _original_unknown
-proc unknown {name args} {
- if {[uplevel [list info exists ^$name]]} {
- apply [uplevel [list set ^$name]] {*}$args
- } else {
- uplevel [list _original_unknown $name {*}$args]
- }
-}
+source "lib/language.tcl"
# invoke at top level, add/remove independent 'axioms' for the system
proc Assert {args} {
@@ -127,15 +117,22 @@ proc Every {event args} {
uplevel [list When {*}$pattern "$body\nEvaluator::Unmatch $level"]
}
}
+
proc On {event args} {
if {$event eq "process"} {
if {[llength $args] == 2} {
lassign $args name body
} elseif {[llength $args] == 1} {
- set name "${::matchId}-process"
+ # Generate a unique name.
+ set this [uplevel {expr {[info exists this] ? $this : "<unknown>"}}]
+ set subprocessId [uplevel {incr __subprocessId}]
+ set name "${this}-${::matchId}-${subprocessId}"
set body [lindex $args 0]
}
- uplevel [list On-process $name $body]
+ # Serialize the lexical environment at the callsite so we can
+ # send that to the subprocess.
+ lassign [uplevel Evaluator::serializeEnvironment] argNames argValues
+ uplevel [list On-process $name [list apply [list $argNames $body] {*}$argValues]]
} elseif {$event eq "unmatch"} {
set body [lindex $args 0]
@@ -158,9 +155,8 @@ proc After {n unit body} {
}
set ::committed [dict create]
proc Commit {args} {
- upvar this this
set body [lindex $args end]
- set key [list Commit [expr {[info exists this] ? $this : "<unknown>"}] {*}[lreplace $args end end]]
+ set key [list Commit [uplevel {expr {[info exists this] ? $this : "<unknown>"}}] {*}[lreplace $args end end]]
lassign [uplevel Evaluator::serializeEnvironment] argNames argValues
set lambda [list {this} [list apply [list $argNames $body] {*}$argValues]]
Assert $key has program $lambda
@@ -170,40 +166,36 @@ proc Commit {args} {
dict set ::committed $key $lambda
}
-set ::nodename "[info hostname]-[pid]"
-
set ::stepCount 0
set ::stepTime "none"
source "lib/peer.tcl"
-proc Step {} {
+proc StepImpl {} {
incr ::stepCount
- Assert $::nodename has step count $::stepCount
- Retract $::nodename has step count [expr {$::stepCount - 1}]
- set ::stepTime [time {Evaluator::Evaluate}]
+ Assert $::thisProcess has step count $::stepCount
+ Retract $::thisProcess has step count [expr {$::stepCount - 1}]
+
+ Evaluator::Evaluate
if {[namespace exists Display]} {
Display::commit ;# TODO: this is weird, not right level
}
- foreach peerNs [namespace children Peers] {
+ foreach peerNs [namespace children ::Peers] {
apply [list {peer} {
variable connected
if {!$connected} { return }
set shareStatements [clauseset create]
- if {[llength [Statements::findMatches [list /someone/ wishes $::nodename shares all statements]]] > 0} {
- dict for {_ stmt} [Statements::all] {
+ 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}]
+ dict for {_ stmt} [Statements::all] {
+ if {($shareAllWishes && [lindex [statement clause $stmt] 1] eq "wishes") ||
+ ($shareAllClaims && [lindex [statement clause $stmt] 1] eq "claims")} {
clauseset add shareStatements [statement clause $stmt]
}
- } elseif {[llength [Statements::findMatches [list /someone/ wishes $::nodename shares all claims]]] > 0} {
- dict for {_ stmt} [Statements::all] {
- if {[lindex [statement clause $stmt] 1] eq "claims"} {
- clauseset add shareStatements [statement clause $stmt]
- }
- }
}
- set matches [Statements::findMatches [list /someone/ wishes $::nodename shares statements like /pattern/]]
+ set matches [Statements::findMatches [list /someone/ wishes $::thisProcess shares statements like /pattern/]]
lappend matches {*}[Statements::findMatches [list /someone/ wishes $peer receives statements like /pattern/]]
foreach m $matches {
set pattern [dict get $m pattern]
@@ -215,12 +207,17 @@ proc Step {} {
}
variable prevShareStatements
- set shareAssertStatements [clauseset clauses [clauseset minus $shareStatements $prevShareStatements]]
- set shareRetractStatements [clauseset clauses [clauseset minus $prevShareStatements $shareStatements]]
+ set shareAssertStatements [clauseset difference $shareStatements $prevShareStatements]
+ set shareRetractStatements [clauseset difference $prevShareStatements $shareStatements]
if {[llength $shareAssertStatements] > 0 || [llength $shareRetractStatements] > 0} {
- run [list apply {{shareAssertStatements shareRetractStatements} {
- foreach stmt $shareAssertStatements { Assert {*}$stmt }
- foreach stmt $shareRetractStatements { Retract {*}$stmt }
+ run [list apply {{receivedAssertStatements receivedRetractStatements} {
+ upvar [uplevel {namespace current}]::prevReceivedStatements prevReceivedStatements
+ # TODO: Just track process provenance in the statements?
+ set prevReceivedStatements [clauseset union $prevReceivedStatements $receivedAssertStatements]
+ set prevReceivedStatements [clauseset difference $prevReceivedStatements $receivedRetractStatements]
+
+ dict for {stmt _} $receivedAssertStatements { Assert {*}$stmt }
+ dict for {stmt _} $receivedRetractStatements { Retract {*}$stmt }
Step
}} $shareAssertStatements $shareRetractStatements]
}
@@ -228,6 +225,7 @@ proc Step {} {
} $peerNs] [namespace tail $peerNs]
}
}
+proc Step {} { set ::stepTime [time StepImpl] }
source "lib/math.tcl"
@@ -242,9 +240,66 @@ Assert when /__this/ has program code /__programCode/ {{__this __programCode} {
Claim $__this has program [list {this} $__programCode]
}}
+set ::thisNode "[info hostname]"
+set ::nodename $::thisNode ;# for backward compat
+
+namespace eval ::Heap {
+ # Folk has a shared heap among all processes on a given node
+ # (physical machine).
+
+ # Memory allocated from the Folk heap should be accessible, at
+ # exactly the same virtual address, from any Folk process.
+
+ proc init {} {
+ variable cc [c create]
+ $cc include <sys/mman.h>
+ $cc include <sys/stat.h>
+ $cc include <fcntl.h>
+ $cc include <unistd.h>
+ $cc include <stdlib.h>
+ $cc code {
+ size_t folkHeapSize = 100000000; // 100MB
+ uint8_t* folkHeapBase;
+ uint8_t* _Atomic folkHeapPointer;
+ }
+ # The memory mapping of the heap will be inherited by all
+ # subprocesses, since it's established before the creation of
+ # the zygote.
+ $cc proc folkHeapMount {} void {
+ int fd = shm_open("/folk-heap", O_RDWR | O_CREAT, S_IROTH | S_IWOTH | S_IRUSR | S_IWUSR);
+ ftruncate(fd, folkHeapSize);
+ folkHeapBase = (uint8_t*) mmap(0, folkHeapSize,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (folkHeapBase == NULL) {
+ fprintf(stderr, "heapMount: failed"); exit(1);
+ }
+ folkHeapPointer = folkHeapBase;
+ }
+ $cc proc folkHeapAlloc {size_t sz} void* {
+ if (folkHeapPointer + sz > folkHeapBase + folkHeapSize) {
+ fprintf(stderr, "heapAlloc: out of memory"); exit(1);
+ }
+ void* ptr = folkHeapPointer;
+ folkHeapPointer = folkHeapPointer + sz;
+ return (void*) ptr;
+ }
+ if {$::tcl_platform(os) eq "Linux"} {
+ $cc cflags -lrt
+ c loadlib [lindex [exec /usr/sbin/ldconfig -p | grep librt.so | head -1] end]
+ }
+ $cc compile
+ folkHeapMount
+ }
+}
+Heap::init
+
if {[info exists ::entry]} {
- # This all only runs if we're in a primary Folk process; we don't
- # want it to run in subprocesses (which also run main.tcl).
+ source "lib/process.tcl"
+ Zygote::init
+
+ # Everything below here only runs if we're in the primary Folk
+ # process.
+ set ::thisProcess $::thisNode
proc ::loadVirtualPrograms {} {
set ::rootVirtualPrograms [dict create]
@@ -258,7 +313,7 @@ if {[info exists ::entry]} {
{*}[glob -nocomplain "user-programs/[info hostname]/*.folk"]] {
loadProgram $programFilename
}
- Assert $::nodename is providing root virtual programs $::rootVirtualPrograms
+ Assert $::thisNode is providing root virtual programs $::rootVirtualPrograms
# So we can retract them all at once if some other node connects and
# wants to impose its root virtual programs:
@@ -272,7 +327,7 @@ if {[info exists ::entry]} {
# Are there foreign root virtual programs that should take priority over ours?
foreach root $roots {
- if {[dict get $root node] ne $::nodename} {
+ if {[dict get $root node] ne $::thisNode} {
set chosenRoot $root
break
}
@@ -318,12 +373,11 @@ if {[info exists ::entry]} {
}
dict set ::rootVirtualPrograms $programName $programCode
- Assert $::nodename is providing root virtual programs $::rootVirtualPrograms
- Retract $::nodename is providing root virtual programs $oldRootVirtualPrograms
+ Assert $::thisNode is providing root virtual programs $::rootVirtualPrograms
+ Retract $::thisNode is providing root virtual programs $oldRootVirtualPrograms
Step
}
- source "lib/process.tcl"
source "./web.tcl"
source $::entry
}
diff --git a/pi/AprilTags.tcl b/pi/AprilTags.tcl
new file mode 100644
index 00000000..a489a1f4
--- /dev/null
+++ b/pi/AprilTags.tcl
@@ -0,0 +1,63 @@
+source "pi/cUtils.tcl"
+
+namespace eval AprilTags {
+ rename [c create] apc
+ apc cflags -I$::env(HOME)/apriltag
+ apc include <apriltag.h>
+ apc include <tagStandard52h13.h>
+ apc include <math.h>
+ apc include <assert.h>
+ apc code {
+ apriltag_detector_t *td;
+ apriltag_family_t *tf;
+ }
+ defineImageType apc
+
+ apc proc detectInit {} void {
+ td = apriltag_detector_create();
+ tf = tagStandard52h13_create();
+ apriltag_detector_add_family_bits(td, tf, 1);
+ td->nthreads = 2;
+ }
+
+ 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 };
+
+ zarray_t *detections = apriltag_detector_detect(td, &im);
+ int detectionCount = zarray_size(detections);
+
+ Tcl_Obj* detectionObjs[detectionCount];
+ for (int i = 0; i < detectionCount; i++) {
+ apriltag_detection_t *det;
+ zarray_get(detections, i, &det);
+
+ int size = sqrt((det->p[0][0] - det->p[1][0])*(det->p[0][0] - det->p[1][0]) + (det->p[0][1] - det->p[1][1])*(det->p[0][1] - det->p[1][1]));
+ detectionObjs[i] = Tcl_ObjPrintf("id %d center {%f %f} corners {{%f %f} {%f %f} {%f %f} {%f %f}} size %d",
+ det->id,
+ det->c[0], det->c[1],
+ det->p[0][0], det->p[0][1],
+ det->p[1][0], det->p[1][1],
+ det->p[2][0], det->p[2][1],
+ det->p[3][0], det->p[3][1],
+ size);
+ }
+
+
+ zarray_destroy(detections);
+ Tcl_Obj* result = Tcl_NewListObj(detectionCount, detectionObjs);
+ return result;
+ }
+
+ apc proc detectCleanup {} void {
+ tagStandard52h13_destroy(tf);
+ apriltag_detector_destroy(td);
+ }
+
+ c loadlib $::env(HOME)/apriltag/libapriltag.so
+ apc compile
+
+ proc init {} {
+ detectInit
+ }
+}
diff --git a/pi/Camera.tcl b/pi/Camera.tcl
index 5cbc3dbb..963d1261 100644
--- a/pi/Camera.tcl
+++ b/pi/Camera.tcl
@@ -1,246 +1,258 @@
source "lib/c.tcl"
source "pi/cUtils.tcl"
-rename [c create] camc
-
-camc include <string.h>
-camc include <math.h>
+namespace eval Camera {
+ rename [c create] camc
-camc include <errno.h>
-camc include <fcntl.h>
-camc include <sys/ioctl.h>
-camc include <sys/mman.h>
-camc include <asm/types.h>
-camc include <linux/videodev2.h>
+ camc include <string.h>
+ camc include <math.h>
-camc include <stdint.h>
-camc include <stdlib.h>
+ camc include <errno.h>
+ camc include <fcntl.h>
+ camc include <sys/ioctl.h>
+ camc include <sys/mman.h>
+ camc include <asm/types.h>
+ camc include <linux/videodev2.h>
-camc include <jpeglib.h>
+ camc include <stdint.h>
+ camc include <stdlib.h>
-camc struct buffer_t {
- uint8_t* start;
- size_t length;
-}
-camc struct camera_t {
- int fd;
- uint32_t width;
- uint32_t height;
- size_t buffer_count;
- buffer_t* buffers;
- buffer_t head;
-}
+ camc include <jpeglib.h>
-camc code {
- void quit(const char* msg) {
- fprintf(stderr, "[%s] %d: %s\n", msg, errno, strerror(errno));
- exit(1);
+ camc struct buffer_t {
+ uint8_t* start;
+ size_t length;
}
+ camc struct camera_t {
+ int fd;
+ uint32_t width;
+ uint32_t height;
+ size_t buffer_count;
+ buffer_t* buffers;
+ buffer_t head;
+ }
+
+ camc code {
+ uint8_t* folkImagesBase;
+
+ void quit(const char* msg) {
+ fprintf(stderr, "[%s] %d: %s\n", msg, errno, strerror(errno));
+ exit(1);
+ }
- int xioctl(int fd, int request, void* arg) {
- for (int i = 0; i < 100; i++) {
- int r = ioctl(fd, request, arg);
- if (r != -1 || errno != EINTR) return r;
- printf("[%x][%d] %s\n", request, i, strerror(errno));
+ int xioctl(int fd, int request, void* arg) {
+ for (int i = 0; i < 100; i++) {
+ int r = ioctl(fd, request, arg);
+ if (r != -1 || errno != EINTR) return r;
+ printf("[%x][%d] %s\n", request, i, strerror(errno));
+ }
+ return -1;
}
- return -1;
}
-}
-defineImageType camc
-
-camc proc cameraOpen {char* device int width int height} camera_t* {
- printf("device [%s]\n", device);
- int fd = open(device, O_RDWR | O_NONBLOCK, 0);
- if (fd == -1) quit("open");
- camera_t* camera = ckalloc(sizeof (camera_t));
- camera->fd = fd;
- camera->width = width;
- camera->height = height;
- camera->buffer_count = 0;
- camera->buffers = NULL;
- camera->head.length = 0;
- camera->head.start = NULL;
- return camera;
-}
-
-camc proc cameraInit {camera_t* camera} void {
- struct v4l2_capability cap;
- if (xioctl(camera->fd, VIDIOC_QUERYCAP, &cap) == -1) quit("VIDIOC_QUERYCAP");
- if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) quit("no capture");
- if (!(cap.capabilities & V4L2_CAP_STREAMING)) quit("no streaming");
-
- struct v4l2_format format;
- memset(&format, 0, sizeof format);
- format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- format.fmt.pix.width = camera->width;
- format.fmt.pix.height = camera->height;
- format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
- format.fmt.pix.field = V4L2_FIELD_NONE;
- if (xioctl(camera->fd, VIDIOC_S_FMT, &format) == -1) quit("VIDIOC_S_FMT");
-
- struct v4l2_requestbuffers req;
- memset(&req, 0, sizeof req);
- req.count = 4;
- req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- req.memory = V4L2_MEMORY_MMAP;
- if (xioctl(camera->fd, VIDIOC_REQBUFS, &req) == -1) quit("VIDIOC_REQBUFS");
- camera->buffer_count = req.count;
- camera->buffers = calloc(req.count, sizeof (buffer_t));
-
- size_t buf_max = 0;
- for (size_t i = 0; i < camera->buffer_count; i++) {
- struct v4l2_buffer buf;
- memset(&buf, 0, sizeof buf);
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- buf.index = i;
- if (xioctl(camera->fd, VIDIOC_QUERYBUF, &buf) == -1)
- quit("VIDIOC_QUERYBUF");
- if (buf.length > buf_max) buf_max = buf.length;
- camera->buffers[i].length = buf.length;
- camera->buffers[i].start =
- mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
- camera->fd, buf.m.offset);
- if (camera->buffers[i].start == MAP_FAILED) quit("mmap");
+ defineImageType camc
+
+ camc proc cameraOpen {char* device int width int height} camera_t* {
+ printf("device [%s]\n", device);
+ int fd = open(device, O_RDWR | O_NONBLOCK, 0);
+ if (fd == -1) quit("open");
+ camera_t* camera = ckalloc(sizeof (camera_t));
+ camera->fd = fd;
+ camera->width = width;
+ camera->height = height;
+ camera->buffer_count = 0;
+ camera->buffers = NULL;
+ camera->head.length = 0;
+ camera->head.start = NULL;
+ return camera;
}
- camera->head.start = ckalloc(buf_max);
- printf("camera %d; bufcount %zu\n", camera->fd, camera->buffer_count);
-}
+ camc proc cameraInit {camera_t* camera} void {
+ struct v4l2_capability cap;
+ if (xioctl(camera->fd, VIDIOC_QUERYCAP, &cap) == -1) quit("VIDIOC_QUERYCAP");
+ if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) quit("no capture");
+ if (!(cap.capabilities & V4L2_CAP_STREAMING)) quit("no streaming");
+
+ struct v4l2_format format;
+ memset(&format, 0, sizeof format);
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = camera->width;
+ format.fmt.pix.height = camera->height;
+ format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
+ format.fmt.pix.field = V4L2_FIELD_NONE;
+ if (xioctl(camera->fd, VIDIOC_S_FMT, &format) == -1) quit("VIDIOC_S_FMT");
+
+ struct v4l2_requestbuffers req;
+ memset(&req, 0, sizeof req);
+ req.count = 4;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+ if (xioctl(camera->fd, VIDIOC_REQBUFS, &req) == -1) quit("VIDIOC_REQBUFS");
+ camera->buffer_count = req.count;
+ camera->buffers = calloc(req.count, sizeof (buffer_t));
+
+ size_t buf_max = 0;
+ for (size_t i = 0; i < camera->buffer_count; i++) {
+ struct v4l2_buffer buf;
+ memset(&buf, 0, sizeof buf);
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+ if (xioctl(camera->fd, VIDIOC_QUERYBUF, &buf) == -1)
+ quit("VIDIOC_QUERYBUF");
+ if (buf.length > buf_max) buf_max = buf.length;
+ camera->buffers[i].length = buf.length;
+ camera->buffers[i].start =
+ mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
+ camera->fd, buf.m.offset);
+ if (camera->buffers[i].start == MAP_FAILED) quit("mmap");
+ }
+ camera->head.start = ckalloc(buf_max);
-camc proc cameraStart {camera_t* camera} void {
- for (size_t i = 0; i < camera->buffer_count; i++) {
- struct v4l2_buffer buf;
- memset(&buf, 0, sizeof buf);
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- buf.index = i;
- if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) quit("VIDIOC_QBUF");
- printf("camera_start(%zu): %s\n", i, strerror(errno));
+ printf("camera %d; bufcount %zu\n", camera->fd, camera->buffer_count);
}
- enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (xioctl(camera->fd, VIDIOC_STREAMON, &type) == -1)
+ camc proc cameraStart {camera_t* camera} void {
+ for (size_t i = 0; i < camera->buffer_count; i++) {
+ struct v4l2_buffer buf;
+ memset(&buf, 0, sizeof buf);
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+ if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) quit("VIDIOC_QBUF");
+ printf("camera_start(%zu): %s\n", i, strerror(errno));
+ }
+
+ enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (xioctl(camera->fd, VIDIOC_STREAMON, &type) == -1)
quit("VIDIOC_STREAMON");
-}
+ }
-camc code {
-int camera_capture(camera_t* camera) {
- struct v4l2_buffer buf;
- memset(&buf, 0, sizeof buf);
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- if (xioctl(camera->fd, VIDIOC_DQBUF, &buf) == -1) {
- fprintf(stderr, "camera_capture: VIDIOC_DQBUF failed: %d: %s\n", errno, strerror(errno));
- return 0;
+ camc code {
+ int camera_capture(camera_t* camera) {
+ struct v4l2_buffer buf;
+ memset(&buf, 0, sizeof buf);
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ if (xioctl(camera->fd, VIDIOC_DQBUF, &buf) == -1) {
+ fprintf(stderr, "camera_capture: VIDIOC_DQBUF failed: %d: %s\n", errno, strerror(errno));
+ return 0;
+ }
+ memcpy(camera->head.start, camera->buffers[buf.index].start, buf.bytesused);
+ camera->head.length = buf.bytesused;
+ if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) {
+ fprintf(stderr, "camera_capture: VIDIOC_QBUF failed: %d: %s\n", errno, strerror(errno));
+ return 0;
+ }
+ return 1;
+ }
}
- memcpy(camera->head.start, camera->buffers[buf.index].start, buf.bytesused);
- camera->head.length = buf.bytesused;
- if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) {
- fprintf(stderr, "camera_capture: VIDIOC_QBUF failed: %d: %s\n", errno, strerror(errno));
- return 0;
+
+ camc proc cameraFrame {camera_t* camera} int {
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(camera->fd, &fds);
+ int r = select(camera->fd + 1, &fds, 0, 0, &timeout);
+ // printf("r: %d\n", r);
+ if (r == -1) quit("select");
+ if (r == 0) {
+ printf("selection failed of fd %d\n", camera->fd);
+ return 0;
+ }
+ return camera_capture(camera);
}
- return 1;
-}
-}
-camc proc cameraFrame {camera_t* camera} int {
- struct timeval timeout;
- timeout.tv_sec = 1;
- timeout.tv_usec = 0;
-
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(camera->fd, &fds);
- int r = select(camera->fd + 1, &fds, 0, 0, &timeout);
- // printf("r: %d\n", r);
- if (r == -1) quit("select");
- if (r == 0) {
- printf("selection failed of fd %d\n", camera->fd);
- return 0;
+ camc proc cameraDecompressRgb {camera_t* camera image_t dest} void {
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, camera->head.start, camera->head.length);
+ if (jpeg_read_header(&cinfo, TRUE) != 1) {
+ printf("Fail\n");
+ exit(1);
+ }
+ jpeg_start_decompress(&cinfo);
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ unsigned char *buffer_array[1];
+ buffer_array[0] = dest.data + (cinfo.output_scanline) * dest.width * cinfo.output_components;
+ jpeg_read_scanlines(&cinfo, buffer_array, 1);
+ }
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
}
- return camera_capture(camera);
-}
+ camc proc cameraDecompressGray {camera_t* camera image_t dest} void {
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, camera->head.start, camera->head.length);
+ if (jpeg_read_header(&cinfo, TRUE) != 1) {
+ printf("Fail\n");
+ exit(1);
+ }
+ cinfo.out_color_space = JCS_GRAYSCALE;
+ jpeg_start_decompress(&cinfo);
-camc proc cameraDecompressRgb {camera_t* camera image_t dest} void {
- struct jpeg_decompress_struct cinfo;
- struct jpeg_error_mgr jerr;
- cinfo.err = jpeg_std_error(&jerr);
- jpeg_create_decompress(&cinfo);
- jpeg_mem_src(&cinfo, camera->head.start, camera->head.length);
- if (jpeg_read_header(&cinfo, TRUE) != 1) {
- printf("Fail\n");
- exit(1);
- }
- jpeg_start_decompress(&cinfo);
-
- while (cinfo.output_scanline < cinfo.output_height) {
- unsigned char *buffer_array[1];
- buffer_array[0] = dest.data + (cinfo.output_scanline) * dest.width * cinfo.output_components;
- jpeg_read_scanlines(&cinfo, buffer_array, 1);
- }
- jpeg_finish_decompress(&cinfo);
- jpeg_destroy_decompress(&cinfo);
-}
-camc proc cameraDecompressGray {camera_t* camera image_t dest} void {
- struct jpeg_decompress_struct cinfo;
- struct jpeg_error_mgr jerr;
- cinfo.err = jpeg_std_error(&jerr);
- jpeg_create_decompress(&cinfo);
- jpeg_mem_src(&cinfo, camera->head.start, camera->head.length);
- if (jpeg_read_header(&cinfo, TRUE) != 1) {
- printf("Fail\n");
- exit(1);
- }
- cinfo.out_color_space = JCS_GRAYSCALE;
- jpeg_start_decompress(&cinfo);
-
- while (cinfo.output_scanline < cinfo.output_height) {
- unsigned char *buffer_array[1];
- buffer_array[0] = dest.data + (cinfo.output_scanline) * dest.width * cinfo.output_components;
- jpeg_read_scanlines(&cinfo, buffer_array, 1);
- }
- jpeg_finish_decompress(&cinfo);
- jpeg_destroy_decompress(&cinfo);
-}
-camc proc rgbToGray {image_t rgb} image_t {
- uint8_t* gray = (uint8_t*) ckalloc(rgb.width * rgb.height * sizeof (uint8_t));
- for (int y = 0; y < rgb.height; y++) {
- for (int x = 0; x < rgb.width; x++) {
- // we're spending 10-20% of camera time here on Pi ... ??
-
- int i = (y * rgb.width + x) * 3;
- uint32_t r = rgb.data[i];
- uint32_t g = rgb.data[i + 1];
- uint32_t b = rgb.data[i + 2];
- // from https://mina86.com/2021/rgb-to-greyscale/
- uint32_t yy = 3567664 * r + 11998547 * g + 1211005 * b;
- gray[y * rgb.width + x] = ((yy + (1 << 23)) >> 24);
+ while (cinfo.output_scanline < cinfo.output_height) {
+ unsigned char *buffer_array[1];
+ buffer_array[0] = dest.data + (cinfo.output_scanline) * dest.width * cinfo.output_components;
+ jpeg_read_scanlines(&cinfo, buffer_array, 1);
}
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ }
+ camc proc rgbToGray {image_t rgb} image_t {
+ uint8_t* gray = calloc(rgb.width * rgb.height, sizeof (uint8_t));
+ for (int y = 0; y < rgb.height; y++) {
+ for (int x = 0; x < rgb.width; x++) {
+ // we're spending 10-20% of camera time here on Pi ... ??
+
+ int i = (y * rgb.width + x) * 3;
+ uint32_t r = rgb.data[i];
+ uint32_t g = rgb.data[i + 1];
+ uint32_t b = rgb.data[i + 2];
+ // from https://mina86.com/2021/rgb-to-greyscale/
+ uint32_t yy = 3567664 * r + 11998547 * g + 1211005 * b;
+ gray[y * rgb.width + x] = ((yy + (1 << 23)) >> 24);
+ }
+ }
+ return (image_t) {
+ .width = rgb.width, .height = rgb.height,
+ .bytesPerRow = rgb.width,
+ .data = gray
+ };
}
- return (image_t) {
- .width = rgb.width, .height = rgb.height,
- .bytesPerRow = rgb.width,
- .data = gray
- };
-}
-camc proc newImage {int width int height int components} image_t {
- return (image_t) {
- .width = width, .height = height,
- .components = components,
- .bytesPerRow = width*components,
- .data = (uint8_t*) ckalloc(width*height*components)
- };
-}
-camc proc freeImage {image_t image} void {
- ckfree(image.data);
-}
+ camc import ::Heap::cc folkHeapAlloc as folkHeapAlloc
+ camc proc newImage {int width int height int components} image_t {
+ if (folkImagesBase == NULL) {
+ folkImagesBase = folkHeapAlloc(50000000); // 50MB
+ }
+
+ // FIXME: This is a hack.
+ static int imageCount = 0;
+ imageCount = (imageCount + 1) % 20;
+
+ uint8_t* data = folkImagesBase + imageCount * (width*components*height);
+ return (image_t) {
+ .width = width,
+ .height = height,
+ .components = components,
+ .bytesPerRow = width*components,
+ .data = data
+ };
+ }
+ camc proc freeImage {image_t image} void {
+ // free(image.data);
+ }
-c loadlib [expr {$tcl_platform(os) eq "Darwin" ? "/opt/homebrew/lib/libjpeg.dylib" : [lindex [exec /usr/sbin/ldconfig -p | grep libjpeg] end]}]
-camc compile
+ c loadlib [expr {$tcl_platform(os) eq "Darwin" ? "/opt/homebrew/lib/libjpeg.dylib" : [lindex [exec /usr/sbin/ldconfig -p | grep libjpeg] end]}]
+ camc compile
-namespace eval Camera {
variable camera
variable WIDTH
@@ -251,7 +263,22 @@ namespace eval Camera {
variable HEIGHT
set WIDTH $width
set HEIGHT $height
-
+
+ try {
+ while {1} {
+ set pid [exec lsof -t "/dev/video0"]
+ if {$pid eq ""} break
+ exec kill -9 $pid
+ }
+ } on error err {
+ puts "got an error when trying to claim the video input as our own: ${err}"
+ }
+
+ # FIXME: This is bad. Something about the state of /dev/video0 makes the ioctl in cameraInit
+ # return "device busy" - sleeping helps...
+ exec sleep .5
+
+ variable camera
set camera [cameraOpen "/dev/video0" $WIDTH $HEIGHT]
cameraInit $camera
cameraStart $camera
@@ -260,7 +287,6 @@ namespace eval Camera {
for {set i 0} {$i < 5} {incr i} {
cameraFrame $camera
}
- set Camera::camera $camera
}
proc frame {} {
@@ -304,70 +330,3 @@ if {([info exists ::argv0] && $::argv0 eq [info script]) || \
freeImage $rgb
}
}
-
-
-namespace eval AprilTags {
- rename [c create] apc
- apc cflags -I$::env(HOME)/apriltag
- apc include <apriltag.h>
- apc include <tagStandard52h13.h>
- apc include <math.h>
- apc include <assert.h>
- apc code {
- apriltag_detector_t *td;
- apriltag_family_t *tf;
- }
- defineImageType apc
-
- apc proc detectInit {} void {
- td = apriltag_detector_create();
- tf = tagStandard52h13_create();
- apriltag_detector_add_family_bits(td, tf, 1);
- td->nthreads = 2;
- }
-
- 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 };
-
- zarray_t *detections = apriltag_detector_detect(td, &im);
- int detectionCount = zarray_size(detections);
-
- Tcl_Obj* detectionObjs[detectionCount];
- for (int i = 0; i < detectionCount; i++) {
- apriltag_detection_t *det;
- zarray_get(detections, i, &det);
-
- int size = sqrt(
- (det->p[0][0] - det->p[1][0]) * (det->p[0][0] - det->p[1][0]) +
- (det->p[0][1] - det->p[1][1]) * (det->p[0][1] - det->p[1][1])
- );
-
- detectionObjs[i] = Tcl_ObjPrintf("id %d center {%f %f} corners {{%f %f} {%f %f} {%f %f} {%f %f}} size %d",
- det->id,
- det->c[0], det->c[1],
- det->p[0][0], det->p[0][1],
- det->p[1][0], det->p[1][1],
- det->p[2][0], det->p[2][1],
- det->p[3][0], det->p[3][1],
- size);
- }
-
-
- zarray_destroy(detections);
- Tcl_Obj* result = Tcl_NewListObj(detectionCount, detectionObjs);
- return result;
- }
-
- apc proc detectCleanup {} void {
- tagStandard52h13_destroy(tf);
- apriltag_detector_destroy(td);
- }
-
- c loadlib $::env(HOME)/apriltag/libapriltag.so
- apc compile
-
- proc init {} {
- detectInit
- }
-}
diff --git a/pi/pi.tcl b/pi/pi.tcl
index a8cdc4d8..e846b688 100644
--- a/pi/pi.tcl
+++ b/pi/pi.tcl
@@ -73,53 +73,6 @@ namespace eval Display {
}
}
-# Camera thread
-namespace eval Camera {
- variable WIDTH 1280
- variable HEIGHT 720
- variable statements [list]
-
- variable cameraThread [thread::create [format {
- source pi/Camera.tcl
- Camera::init %d %d
- AprilTags::init
- puts "Camera tid: [getTid]"
-
- set grayFrames [list]
- while true {
- # Hack: we free old images. Really this should be done on
- # the main thread when it's actually done with them.
- if {[llength $grayFrames] > 10} {
- freeImage [lindex $grayFrames 0]
- set grayFrames [lreplace $grayFrames 0 0]
- }
- set cameraTime [time {
- set grayFrame [Camera::grayFrame]
- set tags [AprilTags::detect $grayFrame]
- lappend grayFrames $grayFrame
- }]
- set statements [list]
- lappend statements [list camera claims the camera time is $cameraTime]
- lappend statements [list camera claims the camera frame is $grayFrame]
- foreach tag $tags {
- lappend statements [list camera claims tag [dict get $tag id] has center [dict get $tag center] size [dict get $tag size]]
- lappend statements [list camera claims tag [dict get $tag id] has corners [dict get $tag corners]]
- }
-
- # send this script back to the main Folk thread
- # puts "\n\nCommands\n-----\n[join $commands \"\n\"]"
- thread::send -async "%s" [list set Camera::statements $statements]
- }
- } $WIDTH $HEIGHT [thread::id]]]
- puts "Camera thread id: $cameraThread"
-
- Assert when $::nodename has step count /c/ {{c} {
- foreach stmt $Camera::statements {
- Say {*}$stmt
- }
- }}
-}
-
try {
set keyboardThread [thread::create [format {
source "pi/Keyboard.tcl"
diff --git a/play/fork-play.tcl b/play/fork-play.tcl
new file mode 100644
index 00000000..920a8987
--- /dev/null
+++ b/play/fork-play.tcl
@@ -0,0 +1,23 @@
+source "lib/c.tcl"
+set cc [c create]
+$cc include <unistd.h>
+$cc proc ::fork {} int {
+ return fork();
+}
+$cc compile
+
+puts "In parent ([pid]). Forking"
+set pid [fork]
+if {$pid == 0} {
+ puts "In child ([pid]). Forking"
+ set pid2 [fork]
+ if {$pid2 == 0} {
+ puts "In grandchild ([pid]). Done"
+ exit 0
+ }
+ puts "In child. Done"
+ exit 0
+}
+
+puts "In parent. Done"
+while true {}
diff --git a/play/zygote-play.tcl b/play/zygote-play.tcl
new file mode 100644
index 00000000..358e59cd
--- /dev/null
+++ b/play/zygote-play.tcl
@@ -0,0 +1,60 @@
+source "lib/c.tcl"
+set cc [c create]
+$cc include <unistd.h>
+$cc proc ::fork {} int {
+ return fork();
+}
+$cc compile
+
+namespace eval Zygote {
+ proc init {} {
+ variable writer
+ lassign [chan pipe] reader writer
+ set pid [fork]
+ if {$pid == 0} {
+ # We're in the child (the zygote). We will block waiting
+ # for commands from the parent (the original/main thread).
+ close $writer
+
+ fconfigure $reader -buffering line
+ # Zygote's main loop:
+ set script ""
+ while {[gets $reader line] != -1} {
+ append script $line\n
+ if {[info complete $script]} {
+ # FIXME: This fork breaks it.
+ set pid [fork]
+ if {$pid == 0} {
+ eval $script
+ exit 0
+ }
+ set script ""
+ }
+ }
+ exit 0
+
+ } else {
+ # We're still in the parent. The child (the zygote) is $pid.
+ close $reader
+ # We will send the zygote a message every time we want it to
+ # fork.
+ fconfigure $writer -buffering line
+ }
+ }
+ proc spawn {code} {
+ variable writer
+ puts $writer $code
+ }
+}
+
+Zygote::init
+
+Zygote::spawn {
+ puts "hello from [pid]"
+}
+Zygote::spawn {
+ puts "wow from [pid]"
+}
+
+after 3000 { puts done; set ::done true }
+vwait ::done
diff --git a/test/basic.tcl b/test/basic.tcl
index af2bb16f..c39916dd 100644
--- a/test/basic.tcl
+++ b/test/basic.tcl
@@ -1,9 +1,3 @@
-proc assert condition {
- set s "{$condition}"
- if {![uplevel 1 expr $s]} {
- return -code error "assertion failed: $condition"
- }
-}
proc count condition {
Statements::count $condition
}
@@ -11,7 +5,7 @@ proc count condition {
Assert programOakland has program {{this} {
Claim Omar lives in "Oakland"
}}
-Assert when $::nodename has step count /c/ {{c} {
+Assert when $::thisProcess has step count /c/ {{c} {
When Omar lives in /place/ {
Claim $place is a place where Omar lives
}
diff --git a/test/commit.tcl b/test/commit.tcl
index 2a8bdaed..1cf780b8 100644
--- a/test/commit.tcl
+++ b/test/commit.tcl
@@ -1,10 +1,3 @@
-proc assert condition {
- set s "{$condition}"
- if {![uplevel 1 expr $s]} {
- return -code error "assertion failed: $condition"
- }
-}
-
Assert programBall has program {{this} {
Commit { Claim $this has a ball at x 100 y 100 }
diff --git a/test/cstructs.tcl b/test/cstructs.tcl
index 17f0fb41..623cd53f 100644
--- a/test/cstructs.tcl
+++ b/test/cstructs.tcl
@@ -1,10 +1,3 @@
-proc assert condition {
- set s "{$condition}"
- if {![uplevel 1 expr $s]} {
- return -code error "assertion failed: $condition"
- }
-}
-
set cc [c create]
$cc struct Name {
char* first;
@@ -25,3 +18,19 @@ $cc compile
puts [omar]
assert {[dict get [omar] name last] eq "Rizwan"}
+
+set cc [c create]
+$cc proc plusone {int a} int {
+ return a + 1;
+}
+$cc proc dostuff {void* v} int {
+ return 300;
+}
+$cc compile
+assert {[plusone 3] eq 4}
+
+catch {plusone Wrong} err
+assert {[string match {expected integer but got "Wrong"*} $err]}
+
+catch {dostuff hi} err
+assert {[string match {failed to convert argument from Tcl to C*} $err]}
diff --git a/test/joins.tcl b/test/joins.tcl
index 799abe0c..57d80300 100644
--- a/test/joins.tcl
+++ b/test/joins.tcl
@@ -1,10 +1,3 @@
-proc assert condition {
- set s "{$condition}"
- if {![uplevel 1 expr $s]} {
- return -code error "assertion failed: $condition"
- }
-}
-
Assert Omar is a person
Assert Omar lives in "New York"
Assert Elmo is a person
diff --git a/test/negation.tcl b/test/negation.tcl
index 0067783a..501d7295 100644
--- a/test/negation.tcl
+++ b/test/negation.tcl
@@ -1,9 +1,3 @@
-proc assert condition {
- set s "{$condition}"
- if {![uplevel 1 expr $s]} {
- return -code error "assertion failed: $condition"
- }
-}
Assert programNegation has program code {
When /nobody/ is booping {
set ::booping nope
diff --git a/test/perf.tcl b/test/perf.tcl
index 97ee79cc..186f997c 100644
--- a/test/perf.tcl
+++ b/test/perf.tcl
@@ -9,22 +9,22 @@ Assert when /name/ is a /animal/ {{name animal} {
Assert when /node/ has step count /c/ {{node c} {}}
Assert Bob is a cat
-puts "$::nodename: No additional statements:"
+puts "$::thisProcess: No additional statements:"
puts " [run]"
for {set i 0} {$i < 100} {incr i} { Assert $i }
-puts "$::nodename: Asserted 100 statements:"
+puts "$::thisProcess: Asserted 100 statements:"
puts " [run]"
Assert Omar is a human
-puts "$::nodename: Asserted 100 statements + Omar is a human:"
+puts "$::thisProcess: Asserted 100 statements + Omar is a human:"
puts " [run]"
-puts "$::nodename: Same:"
+puts "$::thisProcess: Same:"
puts " [run]"
-puts "$::nodename: Same:"
+puts "$::thisProcess: Same:"
puts " [run]"
-puts "$::nodename: Same:"
+puts "$::thisProcess: Same:"
puts " [run]"
diff --git a/test/process.tcl b/test/process.tcl
index 72dcb264..c69fe26c 100644
--- a/test/process.tcl
+++ b/test/process.tcl
@@ -8,8 +8,7 @@ Step
Assert when we are running {{} {
On process {
- Assert <root> claims things are good
- Step
+ Claim things are good
}
When things are good {
@@ -17,24 +16,70 @@ Assert when we are running {{} {
}
}}
Step
-vwait good
+vwait ::good
Assert when we are running {{} {
- puts "Core: $::nodename"
On process {
set n 0
while true {
incr n
Commit { Claim the counter is $n }
- Step
+ if {$n > 10} { break }
}
}
When the counter is /n/ {
if {$n > 5} {
- set ::done true
+ set ::ok true
}
}
}}
Step
-vwait done
+vwait ::ok
+
+Assert when we are running {{} {
+ On process {
+ Claim I am in a process
+ When I am in a process {
+ Commit { Claim we were in a process }
+ }
+ }
+ When we were in a process {
+ set ::wereinaprocess true
+ }
+}}
+Step
+vwait ::wereinaprocess
+
+Assert when we are running {{} {
+ On process {
+ Wish $::thisProcess receives statements like [list /x/ claims the main process exists]
+ When the main process exists {
+ Commit { Claim the subprocess heard that the main process exists }
+ }
+ }
+ Claim the main process exists
+ When the subprocess heard that the main process exists {
+ set ::heard true
+ }
+}}
+Step
+vwait ::heard
+
+Retract when we are running /anything/
+Step
+
+Assert when we are running {{} {
+ set x done
+ On process {
+ eval [python3 [subst {
+ print("Claim Python is $x")
+ }]]
+ }
+ When Python is done {
+ set ::pythondone true
+ }
+}}
+Step
+
+vwait ::pythondone
diff --git a/test/shm.tcl b/test/shm.tcl
new file mode 100644
index 00000000..c3d63fc7
--- /dev/null
+++ b/test/shm.tcl
@@ -0,0 +1,58 @@
+Assert we are running
+Assert when we are running {{} {
+ On process {
+ set cc [c create]
+ $cc include <sys/mman.h>
+ $cc include <sys/stat.h>
+ $cc include <fcntl.h>
+ $cc include <unistd.h>
+ $cc include <stdlib.h>
+ $cc proc shmMount {char* name size_t size void* addr} void {
+ int fd = shm_open(name, O_RDWR | O_CREAT, S_IROTH | S_IWOTH | S_IRUSR | S_IWUSR);
+ ftruncate(fd, size);
+ void* ptr = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
+ if (ptr == NULL || ptr != addr) {
+ fprintf(stderr, "shmMount: failed"); exit(1);
+ }
+ }
+ $cc proc blup {} void {
+ void* ptr = (void*)0x280000000;
+ shmMount("/folk-images", 1000000000, ptr);
+
+ char* s = (char*)ptr;
+ snprintf(s, 100, "Hello!");
+ }
+ $cc compile
+ blup
+ }
+
+ On process {
+ set cc [c create]
+ $cc include <sys/mman.h>
+ $cc include <sys/stat.h>
+ $cc include <fcntl.h>
+ $cc include <unistd.h>
+ $cc include <stdlib.h>
+ $cc proc shmMount {char* name size_t size void* addr} void {
+ int fd = shm_open(name, O_RDWR | O_CREAT, S_IROTH | S_IWOTH | S_IRUSR | S_IWUSR);
+ ftruncate(fd, size);
+ void* ptr = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
+ if (ptr == NULL || ptr != addr) {
+ fprintf(stderr, "shmMount: failed"); exit(1);
+ }
+ }
+ $cc proc blup {} void {
+ void* ptr = (void*)0x280000000;
+ shmMount("/folk-images", 1000000000, ptr);
+
+ char* s = (char*)ptr;
+ printf("[%s]\n", s);
+ }
+ $cc compile
+ blup
+ }
+}}
+Step
+
+after 1000 {set done true}
+vwait done
diff --git a/test/trie.tcl b/test/trie.tcl
index 9e353d58..92ad9efa 100644
--- a/test/trie.tcl
+++ b/test/trie.tcl
@@ -2,13 +2,6 @@ set t [trie create]
trie add t {Omar is a person} 1
trie add t {Generic is a /y/} 2
-proc assert condition {
- set s "{$condition}"
- if {![uplevel 1 expr $s]} {
- return -code error "assertion failed: $condition"
- }
-}
-
assert {[trie lookup $t {Omar is a person}] eq {1}}
assert {[trie lookup $t {/p/ is a person}] eq {1 2}}
assert {[trie lookup $t {Omar is a /x/}] eq {1}}
diff --git a/test/with-all.tcl b/test/with-all.tcl
index d628a9db..27e594bd 100644
--- a/test/with-all.tcl
+++ b/test/with-all.tcl
@@ -1,14 +1,3 @@
-proc assert condition {
- set s "{$condition}"
- if {![uplevel 1 expr $s]} {
- set errmsg "assertion failed: $condition"
- if {[lindex $condition 1] eq "eq" && [string index [lindex $condition 0] 0] eq "$"} {
- set errmsg "$errmsg\n[uplevel 1 [list set [string range [lindex $condition 0] 1 end]]] is not equal to [lindex $condition 2]"
- }
- return -code error $errmsg
- }
-}
-
Assert programOakland has program code {
Claim Omar lives in "Oakland"
}
diff --git a/user-programs/haippi7/laser-pi.tcl b/user-programs/haippi7/laser-pi.tcl
index 601f2809..19b9702a 100644
--- a/user-programs/haippi7/laser-pi.tcl
+++ b/user-programs/haippi7/laser-pi.tcl
@@ -97,7 +97,7 @@ namespace eval Camera {
} $WIDTH $HEIGHT [thread::id]]]
puts "Camera thread id: $cameraThread"
- Assert when $::nodename has step count /c/ {
+ Assert when $::thisProcess has step count /c/ {
foreach stmt $Camera::statements {
Say {*}$stmt
}
diff --git a/virtual-programs/camera.folk b/virtual-programs/camera.folk
new file mode 100644
index 00000000..b865392e
--- /dev/null
+++ b/virtual-programs/camera.folk
@@ -0,0 +1,37 @@
+if {$::isLaptop} return
+
+namespace eval ::Camera {
+ variable WIDTH 1280
+ variable HEIGHT 720
+}
+
+On process {
+ source pi/Camera.tcl
+ source pi/AprilTags.tcl
+ Camera::init 1280 720
+ AprilTags::init
+
+ puts "Camera tid: [getTid]"
+
+ while true {
+ set cameraTime [time {
+ set grayFrame [Camera::grayFrame]
+ }]
+ set aprilTime [time {
+ set tags [AprilTags::detect $grayFrame]
+ }]
+
+ Commit {
+ Claim the camera frame is $grayFrame
+
+ Claim the camera time is $cameraTime
+ 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]
+ }
+ }
+ Step
+ }
+}
diff --git a/virtual-programs/new-program-web-editor.folk b/virtual-programs/new-program-web-editor.folk
index 692b00e0..5797365b 100644
--- a/virtual-programs/new-program-web-editor.folk
+++ b/virtual-programs/new-program-web-editor.folk
@@ -113,9 +113,9 @@ Wish the web server handles route "/new" with handler {
ws.close();
}
ws.onmessage = (msg) => {
- if (msg.data.startsWith("ERROR:")) {
+ if (msg.data.startsWith("Error:")) {
const errorEl = document.getElementById("error");
- if (msg.data == "ERROR: {}") {
+ if (msg.data === "Error:") {
errorEl.style.backgroundColor = "";
errorEl.innerText = "";
} else {
@@ -153,7 +153,10 @@ Wish the web server handles route "/new" with handler {
if {$::isLaptop} { Step }
`);
setTimeout(() => {
- send(`list ERROR: [Statements::findMatches [list {${program}} has error /err/ with info /errorInfo/]]`);
+ send(`
+set errors [Statements::findMatches [list {${program}} has error /err/ with info /errorInfo/]]
+join [list "Error:" {*}[lmap e $errors {dict get $e errorInfo}]] "\n"
+`);
}, 500);
}
let jobid;
diff --git a/virtual-programs/regions.folk b/virtual-programs/regions.folk
index 1702bbfa..8f320b42 100644
--- a/virtual-programs/regions.folk
+++ b/virtual-programs/regions.folk
@@ -1,5 +1,4 @@
namespace eval ::vec2 {
- namespace import ::tcl::mathop::+ ::tcl::mathop::- ::tcl::mathop::*
proc add {a b} {
list [+ [lindex $a 0] [lindex $b 0]] [+ [lindex $a 1] [lindex $b 1]]
}
@@ -21,8 +20,6 @@ namespace eval ::vec2 {
proc dot {a b} {
expr {[lindex $a 0]*[lindex $b 0] + [lindex $a 1]*[lindex $b 1]}
}
- namespace import ::tcl::mathfunc::max ::tcl::mathfunc::min
- namespace import ::tcl::mathop::/
proc distanceToLineSegment {a v w} {
set l2 [vec2 distance $v $w]
if {$l2 == 0.0} {
diff --git a/virtual-programs/shapes.folk b/virtual-programs/shapes.folk
index 25c615c5..e5a67fe5 100644
--- a/virtual-programs/shapes.folk
+++ b/virtual-programs/shapes.folk
@@ -68,6 +68,8 @@ When /someone/ wishes /p/ draws a /color/ /shape/ offset /offsetVector/ & /p/ ha
set y [expr {$y + $offsetY}]
}
+ # puts "drawing $shape at $x $y"
+
set adjustedWidth [expr {$width * 0.25}]
set x [expr { $x * 1.3}]
set y [expr { $y * 1.25}]
diff --git a/virtual-programs/tags-and-calibration.folk b/virtual-programs/tags-and-calibration.folk
index f3155612..cf252de6 100644
--- a/virtual-programs/tags-and-calibration.folk
+++ b/virtual-programs/tags-and-calibration.folk
@@ -1,5 +1,10 @@
if {$::isLaptop} { return }
+namespace eval ::Camera {
+ variable WIDTH 1280
+ variable HEIGHT 720
+}
+
package require math::linearalgebra
namespace import ::math::linearalgebra::add \
::math::linearalgebra::sub \
diff --git a/virtual-programs/web-editor.folk b/virtual-programs/web-editor.folk
index 6669d9fa..ae14f9b4 100644
--- a/virtual-programs/web-editor.folk
+++ b/virtual-programs/web-editor.folk
@@ -1,117 +1,121 @@
Wish the web server handles route {/page/(.*)$} with handler {
if {[regexp -all {/page/(\d*)$} $path whole_match program_id]} {
- set filename "../folk-printed-programs/$program_id.folk"
+ set filename "../folk-printed-programs/$program_id.folk"
set fp [open $filename r]
set file_data [read $fp]
close $fp
} elseif {[regexp -all {/page/(.*)$} $path whole_match program_id]} {
- set filename "virtual-programs/$program_id.folk"
+ set filename "virtual-programs/$program_id.folk"
set fp [open $filename r]
set file_data [read $fp]
close $fp
}
html [string map [list file_data [htmlEscape $file_data] program_id $program_id file_name $filename] {
- <html>
- <body>
- <div>
- <span id="status">Status</span>
- <button onclick="handleSave()">Save</button>
- <button id="print" onclick="handlePrint()">Print</button>
- </div>
- <textarea id="code" style="width: 100%;height: 95vh;">file_data</textarea>
- <pre id="error"></pre>
- <script>
- const isVirtualProgram = 'file_name'.includes('virtual-programs');
+ <html>
+ <body>
+ <div>
+ <span id="status">Status</span>
+ <button onclick="handleSave()">Save</button>
+ <button id="print" onclick="handlePrint()">Print</button>
+ </div>
+ <textarea id="code" style="width: 100%;height: 95vh;">file_data</textarea>
+ <pre id="error"></pre>
+ <script>
+ const isVirtualProgram = 'file_name'.includes('virtual-programs');
- if (isVirtualProgram) {
- document.getElementById("print").disabled = true;
- }
+ if (isVirtualProgram) {
+ document.getElementById("print").disabled = true;
+ }
- const codeEle = document.getElementById("code");
- function uuidv4() {
- return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
- (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
- );
- }
+ const codeEle = document.getElementById("code");
+ function uuidv4() {
+ return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
+ );
+ }
- // Cmd + S || Ctrl + S => Save
- document.addEventListener('keydown', function(e) {
- if ((window.navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey) && e.keyCode == 83) {
- e.preventDefault();
- handleSave();
- }
- }, false);
- // Cmd + P || Ctrl + P => Print
- document.addEventListener('keydown', function(e) {
- if ((window.navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey) && e.keyCode == 80) {
- e.preventDefault();
- handlePrint();
- }
- }, false);
+ // Cmd + S || Ctrl + S => Save
+ document.addEventListener('keydown', function(e) {
+ if ((window.navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey) && e.keyCode == 83) {
+ e.preventDefault();
+ handleSave();
+ }
+ }, false);
+ // Cmd + P || Ctrl + P => Print
+ document.addEventListener('keydown', function(e) {
+ if ((window.navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey) && e.keyCode == 80) {
+ e.preventDefault();
+ handlePrint();
+ }
+ }, false);
- let ws;
- let send;
- function wsConnect() {
- ws = new WebSocket(window.location.origin.replace("http", "ws") + "/ws");
- send = function(s) { ws.send(s); }
+ let ws;
+ let send;
+ function wsConnect() {
+ ws = new WebSocket(window.location.origin.replace("http", "ws") + "/ws");
+ send = function(s) { ws.send(s); }
- ws.onopen = () => {
- document.getElementById('status').innerHTML = "<span style=background-color:seagreen;color:white;>Connnected</span>";
- };
- ws.onclose = window.onbeforeunload = () => {
- document.getElementById('status').innerHTML = "<span style=background-color:red;color:white;>Disconnnected</span>";
- setTimeout(() => { wsConnect(); }, 1000);
- };
- ws.onerror = (err) => {
- document.getElementById('status').innerText = "Error";
- console.error('Socket encountered error: ', err.message, 'Closing socket');
- ws.close();
- }
- ws.onmessage = (msg) => {
- if (msg.data.startsWith("ERROR:")) {
- const errorEl = document.getElementById("error");
- if (msg.data == "ERROR: {}") {
- errorEl.style.backgroundColor = "";
- errorEl.innerText = "";
- } else {
- errorEl.style.backgroundColor = "#f55";
- errorEl.innerText = msg.data;
- }
- }
- }
- };
- wsConnect();
+ ws.onopen = () => {
+ document.getElementById('status').innerHTML = "<span style=background-color:seagreen;color:white;>Connnected</span>";
+ };
+ ws.onclose = window.onbeforeunload = () => {
+ document.getElementById('status').innerHTML = "<span style=background-color:red;color:white;>Disconnnected</span>";
+ setTimeout(() => { wsConnect(); }, 1000);
+ };
+ ws.onerror = (err) => {
+ document.getElementById('status').innerText = "Error";
+ console.error('Socket encountered error: ', err.message, 'Closing socket');
+ ws.close();
+ }
+ ws.onmessage = (msg) => {
+ if (msg.data.startsWith("Error:")) {
+ const errorEl = document.getElementById("error");
+ if (msg.data === "Error:") {
+ errorEl.style.backgroundColor = "";
+ errorEl.innerText = "";
+ } else {
+ errorEl.style.backgroundColor = "#f55";
+ errorEl.innerText = msg.data;
+ }
+ }
+ }
+ };
+ wsConnect();
- function handleSave() {
- const code = document.getElementById("code").value;
- send(`
- set fp [open file_name w]
- puts -nonewline $fp {${code}}
- close $fp
- puts "Saved program_id.folk"
- `);
+ function handleSave() {
+ const code = document.getElementById("code").value;
+ send(`
+ set fp [open file_name w]
+ puts -nonewline $fp {${code}}
+ close $fp
+ puts "Saved program_id.folk"
+ `);
if (isVirtualProgram) {
send(`EditVirtualProgram file_name {${code}}`)
- }
+ }
- setTimeout(() => {
- send(`list ERROR: [Statements::findMatches [list program_id has error /err/ with info /errorInfo/]]`);
- }, 500);
- }
- let jobid;
- function handlePrint() {
- const code = document.getElementById("code").value;
- jobid = String(Math.random());
- send(`Assert web wishes to print program program_id with code {${code}} with job id {${jobid}}`);
- setTimeout(500, () => {
- send(`Retract web wishes to print program program_id with code {${code}} with job id {${jobid}}`);
- });
- }
- </script>
- </body>
- </html>
+ setTimeout(() => {
+ send(`
+set errors [Statements::findMatches [list program_id has error /err/ with info /errorInfo/]]
+join [list "Error:" {*}[lmap e $errors {dict get $e errorInfo}]] "\n"
+`);
+ }, 500);
+ }
+
+ let jobid;
+ function handlePrint() {
+ const code = document.getElementById("code").value;
+ jobid = String(Math.random());
+ send(`Assert web wishes to print program program_id with code {${code}} with job id {${jobid}}`);
+ setTimeout(500, () => {
+ send(`Retract web wishes to print program program_id with code {${code}} with job id {${jobid}}`);
+ });
+ }
+ </script>
+ </body>
+ </html>
}]
}
diff --git a/web.tcl b/web.tcl
index d2d79c38..40216ecb 100644
--- a/web.tcl
+++ b/web.tcl
@@ -32,7 +32,10 @@ proc handlePage {path contentTypeVar} {
lappend l [subst {
<li>
<details>
- <summary>$id: [htmlEscape [statement short $stmt]]</summary>
+ <summary style="[expr {
+ [lsearch -exact [statement clause $stmt] error] != -1
+ ? "color: red"
+ : ""}]">$id: [htmlEscape [statement short $stmt]]</summary>
<pre>[htmlEscape [statement clause $stmt]]</pre>
</details>
</li>
@@ -101,6 +104,7 @@ proc handlePage {path contentTypeVar} {
proc handleRead {chan addr port} {
chan configure $chan -translation crlf
gets $chan line; set firstline $line
+ # puts "Http: $chan $addr $port: $line"
set headers [list]
while {[gets $chan line] >= 0 && $line ne ""} {
if {[regexp -expanded {^( [^\s:]+ ) \s* : \s* (.+)} $line -> k v]} {
@@ -136,6 +140,7 @@ proc handleRead {chan addr port} {
}
close $chan
} elseif {[::websocket::test $::serverSock $chan "/ws" $headers]} {
+ # puts "WS: $chan $addr $port"
::websocket::upgrade $chan
# from now the handleWS will be called (not anymore handleRead).
} else { puts "Closing: $chan $addr $port $headers"; close $chan }
@@ -143,15 +148,28 @@ proc handleRead {chan addr port} {
proc handleWS {chan type msg} {
if {$type eq "connect" || $type eq "ping" || $type eq "pong"} {
+ # puts "Event $type from chan $chan"
} elseif {$type eq "text"} {
if {[catch {::websocket::send $chan text [eval $msg]} err] == 1} {
if [catch {
- puts stderr "$::nodename: Error on receipt: $err\n$::errorInfo"
+ puts stderr "$::thisProcess: Error on receipt: $err\n$::errorInfo"
::websocket::send $chan text $err
- } err2] { puts "$::nodename: $err2" }
+ } err2] { puts "$::thisProcess: $err2" }
+ }
+ } elseif {$type eq "disconnect"} {
+ foreach peerNs [namespace children ::Peers] {
+ apply [list {disconnectedChan} {
+ variable chan
+ if {$chan eq $disconnectedChan} {
+ variable prevReceivedStatements
+ foreach stmt $prevReceivedStatements {
+ Retract {*}$stmt
+ }
+ }
+ } $peerNs] $chan
}
} else {
- puts "$::nodename: Unhandled WS event $type $msg"
+ puts "$::thisProcess: Unhandled WS event $type on $chan ($msg)"
}
}