Tcl Source Code

Check-in [91aea72dbd]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Streamline demo setup; update README to reflect API changes.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | ferrieux-nacl
Files: files | file ages | folders
SHA1: 91aea72dbdaf8b5f83a5efc218fcfe3d6a5f41f9
User & Date: ferrieux 2011-10-18 22:03:19
Context
2011-10-25
04:21
Add a local copy of reference JS demo at same fps. Add an interactive tclsh page for easy timings, i... Leaf check-in: 29517d12f7 user: ferrieux tags: ferrieux-nacl
2011-10-18
22:03
Streamline demo setup; update README to reflect API changes. check-in: 91aea72dbd user: ferrieux tags: ferrieux-nacl
06:33
Update to new (incompatible, stabilized) ABI. Now requires Chrome 15 or above. check-in: d6b778727b user: ferrieux tags: ferrieux-nacl
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to nacl/Makefile.patch.

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86






87
88
89
90
91
92
93
 all: binaries libraries doc packages
 
-binaries: ${LIB_FILE} $(STUB_LIB_FILE) ${TCL_EXE}
+binaries: ${LIB_FILE} $(STUB_LIB_FILE) tcl$(arch).nexe
 
 libraries:
 
@@ -634,14 +646,30 @@
 # Must be empty so it doesn't conflict with rule for ${TCL_EXE} above
 ${NATIVE_TCLSH}:
 
-Makefile: $(UNIX_DIR)/Makefile.in $(DLTEST_DIR)/Makefile.in
-	$(SHELL) config.status
+init.tcl.c: ../library/init.tcl init.natcl
+	cat $^ | tools/tocstr > $@
+
+tclUnixPort.h: ../unix/tclUnixPort.h tclUnixPort.h.patch
+	cat ../unix/tclUnixPort.h > tclUnixPort.h
+	patch -p0 < tclUnixPort.h.patch
+
+naclMain.o:	naclMain.c init.tcl.c
+	$(CC) -c $(CC_SWITCHES) naclMain.c
+
+tcl$(arch).nexe: naclMain.o libtcl8.6.a
+	$(CCPLUS)  $^ $(LDFLAGS) -o $@
+
+$(TCL_OBJS) $(STUB_LIB_OBJS) $(TCLSH_OBJS) $(TCLTEST_OBJS): tclUnixPort.h
+






+#Makefile: $(UNIX_DIR)/Makefile.in $(DLTEST_DIR)/Makefile.in
+#	$(SHELL) config.status
 #tclConfig.h: $(UNIX_DIR)/tclConfig.h.in
 #	$(SHELL) config.status
 
 clean: clean-packages
 	rm -f *.a *.o libtcl* core errs *~ \#* TAGS *.E a.out \







|




















>
>
>
>
>
>







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
 all: binaries libraries doc packages
 
-binaries: ${LIB_FILE} $(STUB_LIB_FILE) ${TCL_EXE}
+binaries: ${LIB_FILE} $(STUB_LIB_FILE) tcl$(arch).nexe
 
 libraries:
 
@@ -634,14 +646,36 @@
 # Must be empty so it doesn't conflict with rule for ${TCL_EXE} above
 ${NATIVE_TCLSH}:
 
-Makefile: $(UNIX_DIR)/Makefile.in $(DLTEST_DIR)/Makefile.in
-	$(SHELL) config.status
+init.tcl.c: ../library/init.tcl init.natcl
+	cat $^ | tools/tocstr > $@
+
+tclUnixPort.h: ../unix/tclUnixPort.h tclUnixPort.h.patch
+	cat ../unix/tclUnixPort.h > tclUnixPort.h
+	patch -p0 < tclUnixPort.h.patch
+
+naclMain.o:	naclMain.c init.tcl.c
+	$(CC) -c $(CC_SWITCHES) naclMain.c
+
+tcl$(arch).nexe: naclMain.o libtcl8.6.a
+	$(CCPLUS)  $^ $(LDFLAGS) -o $@
+
+$(TCL_OBJS) $(STUB_LIB_OBJS) $(TCLSH_OBJS) $(TCLTEST_OBJS): tclUnixPort.h
+
+server:
+	tclsh trivhttpd.tcl 5103 &
+
+balls:
+	google-chrome http://localhost:5103/demo/balls.html
+
+#Makefile: $(UNIX_DIR)/Makefile.in $(DLTEST_DIR)/Makefile.in
+#	$(SHELL) config.status
 #tclConfig.h: $(UNIX_DIR)/tclConfig.h.in
 #	$(SHELL) config.status
 
 clean: clean-packages
 	rm -f *.a *.o libtcl* core errs *~ \#* TAGS *.E a.out \

Changes to nacl/README.

13
14
15
16
17
18
19
20
21

22

23
24
25
26
27
28
29
30


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
browser (currently,  Chrome only; in the future,  other browsers too),
one could  develop client-side  code mainly in  Tcl, with a  very thin
layer of JS in the page; so, basically, Tcl to control the HTML5 DOM !

Building Tcl for Nacl
---------------------

 - install the NaCl SDK (for me native_client_sdk_0_1_721_0)


 - set your PATH so that nacl-gcc et al become reachable


 - cd (Tcl-source-tree)/nacl 

 - run './configure' (which calls ../unix/configure with proper flags,
   and patches the generated Makefile)

 - run  'make'. This  creates  tcl.nmf and  tcl32.nexe or  tcl64.nexe,
   depending on the kind of x86 you're currently on.



 - (optional) to also build the  other kind, do 'make distclean', then
   './configure -m32  (or -m64)',  then 'make'. The  provided manifest
   file (tcl.nmf) points to both, to be compatible with both builds of
   Chrome.

 - launch the  NaCl-provided python-based  webserver, and make  it see
   your nacl dir somewhere in its document tree. e.g. 
   cd $NACL/examples; ln -s /..../tcl/nacl nacl; python httpd.py 5103

 - (once) open about:flags in Chrome and enable Native Client

 - start   '(cd  tools;./chromedebug)',  and   point  Chrome   to  the
   index.html in nacl. e.g. chromedebug http://localhost:5103/nacl.

 - alternatively, use './chrd' to load the 'balls' demo.

 - (every  time)  make  sure  no  non-chromedebug  chrome  process  is
   running, otherwise it  will be used instead, and  NaCl will fail to
   load. Also, when you update or tweak anything, it is a good idea to
   flush your browser's cache !

 - note:  'chromedebug' just  sets an  env  var for  debug output  (on
   stderr), and  stars chrome with '--no-sandbox',  which is currently
   needed for NaCl to be really enabled on Linux.


Overview of the porting method
------------------------------

Nacl  comes with  a  very incomplete  libc  and set  of headers.   The
strategy,  then,  is to  simply  "plug"  missing  syscalls or  library
functions with either explicitly  failing stubs (returning -1 or NULL,







|

>
|
>






|
|
>
>


|
|
|

<
<
|
|
|

|
<
|
<
|
<
<
<
<

<
<
<
|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40


41
42
43
44
45

46

47




48



49
50
51
52
53
54
55
56
browser (currently,  Chrome only; in the future,  other browsers too),
one could  develop client-side  code mainly in  Tcl, with a  very thin
layer of JS in the page; so, basically, Tcl to control the HTML5 DOM !

Building Tcl for Nacl
---------------------

 - install the NaCl SDK at http://code.google.com/chrome/nativeclient/docs/download.html

 - for now, only use the pepper_14 bundle

 - add (Nacl-SDK-top-dir)/pepper_14/native_client_sdk_0_5_1052/toolchain/linux_x86/bin to your PATH

 - cd (Tcl-source-tree)/nacl 

 - run './configure' (which calls ../unix/configure with proper flags,
   and patches the generated Makefile)

 - run  'make  binaries'.  This  creates  tcl.nmf  and  tcl32.nexe  or
   tcl64.nexe, depending on  the kind of x86 you're  currently on. You
   can also  simply type  'make' but the  libraries are not  usable by
   NaCl yet (no dynamic linking).

 - (optional) to also build the  other kind, do 'make distclean', then
   './configure -m32  (or -m64)',  then 'make binaries'.  The provided
   manifest file (tcl.nmf) points to  both, to be compatible with both
   builds of Chrome.



 - run 'make sever'. This starts  (on port 5103) a tiny, trivial httpd
   written  in Tcl,  whose  sole purpose  is  to server  the few  demo
   files. Note that NaCl is explicitly disabled on file:// urls.

 - (once) open  about:flags in Chrome  and enable Native  Client (note

   this will  be saved  in your per-user  Chrome preferences  and will

   even survive a Chrome upgrade)








 - run 'make balls'

Overview of the porting method
------------------------------

Nacl  comes with  a  very incomplete  libc  and set  of headers.   The
strategy,  then,  is to  simply  "plug"  missing  syscalls or  library
functions with either explicitly  failing stubs (returning -1 or NULL,
72
73
74
75
76
77
78
79
80
81
82

83

84


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
This approach allows to compile Tcl for Nacl without changing a single
line of the original source distribution; all new things are in the
nacl subdir. Also, much is reused from ../unix.

Nacl context specifics
----------------------

One thing to keep in mind is  that NaCl lets us run in our sandbox, in
a  dedicated process, but  at the  time of  writing, it  is in  a very
synchronous  "lockstep"  interaction with  the  main renderer  process
executing  JS  code.  A  more  async  API  allowing  us  to  be  in  a

free-floating thread and exchanging messages  with JS is in the works,

but not baked yet.



Given  this,  NaTcl  simply   obeys  the  rules  of  the  synchronous,
event-driven  world of JS:  do everything  in quickly  returning event
callbacks,  never stick  for too  long.  Note that  most Tcl/Tk  users
should feel at  home, since it is exactly the  recipe for a responsive
Tk GUI.

For maximal genericity, the way NaTcl "retroacts" on the JS context is
by returning a JS string to eval(). Then you can do whatever you want,
including of course  arranging for future JS events  to call back into
NaTcl. See [domset] and [after] (in init.natcl, which is compiled into
the binray .nexe) as two very simple examples.

In   an  universe   without   syscalls,  loading   other  scripts   is
problematic. To circumvent this (and bootstrap the loading of the main
script), [source $url] is  reimplemented over a JS XmlHttpRequest. And
to preserve  the blocking semantics of traditional  [source] while XHR
is  purely async,  this  implementation is  coroutine-based and  calls
[yield] after  starting the request.  When the download  completes, JS







|
<
|
|
>
|
>
|
>
>

|
<
|
<
<


|
|
|
|







65
66
67
68
69
70
71
72

73
74
75
76
77
78
79
80
81
82

83


84
85
86
87
88
89
90
91
92
93
94
95
96
This approach allows to compile Tcl for Nacl without changing a single
line of the original source distribution; all new things are in the
nacl subdir. Also, much is reused from ../unix.

Nacl context specifics
----------------------

 NaCl lets  us run in  our sandbox, in  a dedicated process;  the only

possible interactions with the browser process are:

   - at init time, one of our functions is called

   - then, each side can post a string message to the other one

   - on  reception,   a  callback  is   called  with  the   string  as
     argument. This happens on each side's single thread.

This  asynchronous communication  method ensures  that the  JS context

will never be blocked, whatever the sandboxed child does. 



For maximal genericity, the way NaTcl "retroacts" on the JS context is
by posting  back a JS string to  eval(). Then you can  do whatever you
want, including of course arranging  for future JS events to call back
into NaTcl. See [domset] and [after] (in init.natcl, which is compiled
into the binray .nexe) as two very simple examples.

In   an  universe   without   syscalls,  loading   other  scripts   is
problematic. To circumvent this (and bootstrap the loading of the main
script), [source $url] is  reimplemented over a JS XmlHttpRequest. And
to preserve  the blocking semantics of traditional  [source] while XHR
is  purely async,  this  implementation is  coroutine-based and  calls
[yield] after  starting the request.  When the download  completes, JS
111
112
113
114
115
116
117
118
119
120
121



122
123
124
125

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

This means that all  the init code in the script will  run in the coro
context (for [source]'s  benefit, but that could be  extended to other
things).  But once  the  execution has  fallen  back out  of the  main
script, hopefully after setting up  many (JS) event handlers, it is up
to the app to establish other coro contexts if needed.

The bottom line is this:

   - a simple,  ol'good-Tk-style event-driven script will  not have to
     bother about coros




   - a more  advanced, coro-savvy script  can use coros to  do lengthy
     things (be they computations  of network downloads) while leaving
     the GUI responsive


   - in all cases, [source] works  as usual to fetch scripts over HTTP
     (in   the   same  domain   as   the   page   serving  the   NaTcl
     plugin). Relative URLs work: [source foo.natcl].

   - in all cases, falling back  out of the main scripts is equivalent
     to going  back to the Tk eventloop  in wish (except it  is the JS
     eventloop).


The "Google Balls" demo
-----------------------

If you point your chrome to "balls.html" (eg with chrd), you'll get a
full NaTcl emulation of the nice Javascript demo at:

 http://www.html5canvastutorials.com/labs/html5-canvas-google-bouncing-balls

This uses a canvas emulation script "canv.natcl", which demonstrates a
possible  (among  many)  way  of  organizing  Tcl-JS  interaction  for
graphics.  In the balls demo,  items are never destroyed nor shuffled,
which is  a favourable case for  lazy recompilation of  the JS repaint
function (basically the func is written just once, and only the coords
stored in a global array get updated, hence allowing for JIT compiling
of this function).

Perf measurements: the NaTcl version currently costs 3x the CPU of the
JS version,  so at  40fps it consumes  a full  core of my  2GHz laptop
(against 33% for the JS one). 

One should  note that the pure  string API used precludes  any used of
the  internal reps of  coordinates, so  there are  many string/integer
conversions. To be continued.

Comparison with native Tcl/Tk
-----------------------------

With the  command 'tools/natcl2tk balls.natcl',  you can run  the same
code  in pure  Tcl/Tk,  in a  true wish  canvas.  You can  use it  for
performance and rendering comparisons.

Future work
-----------

 Coming soon: [domget], more [canvas] features, and optimizations ;-)








|

|
|
>
>
>

<
|
|
>













|
|















|
|
|













104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

This means that all  the init code in the script will  run in the coro
context (for [source]'s  benefit, but that could be  extended to other
things).  But once  the  execution has  fallen  back out  of the  main
script, hopefully after setting up  many (JS) event handlers, it is up
to the app to establish other coro contexts if needed.

A few things about GUI and blocking:

   - a simple,  ol'good-Tk-style event-driven script will work as usual

   - lengthy computations  on the Tcl  side will *not* block  the JS's
     side autonomous GUI elements, thanks to the new async interaction
     described above


   - however, to be able ot interrupt the computation from the GUI, or
     to have Tcl code implement part of the GUI reactions, one must of
     course let the Tcl code return quickly.

   - in all cases, [source] works  as usual to fetch scripts over HTTP
     (in   the   same  domain   as   the   page   serving  the   NaTcl
     plugin). Relative URLs work: [source foo.natcl].

   - in all cases, falling back  out of the main scripts is equivalent
     to going  back to the Tk eventloop  in wish (except it  is the JS
     eventloop).


The "Google Balls" demo
-----------------------

If you point your chrome  to "demo/balls.html" (eg with 'make balls'),
you'll get a full NaTcl emulation of the nice Javascript demo at:

 http://www.html5canvastutorials.com/labs/html5-canvas-google-bouncing-balls

This uses a canvas emulation script "canv.natcl", which demonstrates a
possible  (among  many)  way  of  organizing  Tcl-JS  interaction  for
graphics.  In the balls demo,  items are never destroyed nor shuffled,
which is  a favourable case for  lazy recompilation of  the JS repaint
function (basically the func is written just once, and only the coords
stored in a global array get updated, hence allowing for JIT compiling
of this function).

Perf measurements: the NaTcl version currently costs 3x the CPU of the
JS version,  so at  40fps it consumes  a full  core of my  2GHz laptop
(against 33% for the JS one). 

One should note that the  pure string API used currently precludes any
use  of  the   internal  reps  of  coordinates,  so   there  are  many
string/integer conversions. To be continued.

Comparison with native Tcl/Tk
-----------------------------

With the  command 'tools/natcl2tk balls.natcl',  you can run  the same
code  in pure  Tcl/Tk,  in a  true wish  canvas.  You can  use it  for
performance and rendering comparisons.

Future work
-----------

 Coming soon: [domget], more [canvas] features, and optimizations ;-)

Changes to nacl/demo/balls.html.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        	  .addEventListener('load', moduleDidLoad, true);
    	</script>

    <embed
      name='nacl_module'
      id='tcl'
      width=0 height=0
      src='tcl.nmf'
      type='application/x-nacl'
      verbose=0
      source='balls.natcl'>

         <canvas id="canvas" width="578" height="200"></canvas>
         <h2>Status</h2>
         <div id="modstatus">NO-STATUS</div>







|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        	  .addEventListener('load', moduleDidLoad, true);
    	</script>

    <embed
      name='nacl_module'
      id='tcl'
      width=0 height=0
      src='../tcl.nmf'
      type='application/x-nacl'
      verbose=0
      source='balls.natcl'>

         <canvas id="canvas" width="578" height="200"></canvas>
         <h2>Status</h2>
         <div id="modstatus">NO-STATUS</div>

Changes to nacl/demo/balls.natcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
source canv.natcl
#set ::canv_verbose 1
domset statusField "Running..."

# will be retrieved by [domget]
set width 578
set height 200

# animation globals
set t 0
set frameInterval 33

# ball globals
set ballRadius 10

# physics global
set collisionDamper 0.3
set floorFriction [expr {0.0005*$frameInterval}]










|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
source canv.natcl
#set ::canv_verbose 1
domset statusField "Running..."

# will be retrieved by [domget]
set width 578
set height 200

# animation globals
set t 0
set frameInterval 25

# ball globals
set ballRadius 10

# physics global
set collisionDamper 0.3
set floorFriction [expr {0.0005*$frameInterval}]

Deleted nacl/demo/tcl.nmf.

1
2
3
4
5
6
{
  "program": {
    "x86-32": {"url":"../tcl32.nexe"},
    "x86-64": {"url":"../tcl64.nexe"}
  }
}
<
<
<
<
<
<












Deleted nacl/tools/chrd.

1
2
3
4
#! /bin/sh -x
./chromedebug http://localhost:5103/tcl/${1:-balls.html}


<
<
<
<








Deleted nacl/tools/chromedebug.

1
2
3
4
5
6
#! /bin/sh -x

google-chrome --no-sandbox "$@"

#NACLVERBOSITY=2 google-chrome --no-sandbox "$@"
#PPAPI_BROWSER_DEBUG=1 NACL_PLUGIN_DEBUG=1 NACL_PPAPI_PROXY_DEBUG=1 NACLVERBOSITY=15 google-chrome --no-sandbox "$@"
<
<
<
<
<
<












Added nacl/trivhttpd.tcl.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#! /bin/sh
#\
exec tclsh $0 "$@"

if {$argc!=1} {puts stderr "Usage: [file tail $::argv0] <port>";exit 1}
set port [lindex $argv 0]

proc serve {sok h p} {
    fconfigure $sok -translation binary -buffering full
    if {[catch {
	if {[gets $sok line]<0} {error "400 Empty request"}
	if {![regexp {^GET[ 	]+([^ 	]+)} $line -> path]} {error "403 Method Not Supported"}
	puts stderr "$h:$p - GET $path"
	if {[regexp {[.][.]} $path]} {error "403 Forbidden"}
	set ty text/plain
	switch -glob  [file extension $path] {
	    .html {set ty text/html}
	    .nexe - .nmf {set ty application/octet-stream}
	}
	if {[catch {set ff [open .$path r]}]} {error "404 File Not Found"}
	fconfigure $ff -translation binary
	set data [read $ff]
	close $ff
    } err]} {
	puts $sok "HTTP/1.0 $err\r\n\r"
    } else {
	puts -nonewline $sok "HTTP/1.0 200 OK\r\nContent-Type: $ty\r\nContent-Length: [string length $data]\r\n\r\n$data"
    }
    close $sok
}
socket -server serve $port
vwait forever