How can I make a given script to be evaluated after each iteration in vwait forever? - tcl

vwait forever runs the events loop until the exit command.
I have some stuff to do during each iteration of the event loop. How can I do that?

You don't. What you do is schedule regular timer events that do your work. For user interaction, 10 times a second is quite regular enough. To schedule regular timer events, use the every command from the Tcler's Wiki, like this:
proc every {ms body} {after $ms [info level 0]; eval $body}
every 100 {
puts "I'm saying Hi ten times a second!"
}
That's the simplest form of every; more complex cancelable versions are further down that wiki page.

Related

Periodically send command with Tcl

I am writing a Tcl/Tk interface to communicate with a temperature controller over Modbus. I would like to periodically (ie. every 5-10s or so) send a query to the temperature controller to get the current temperature.
Right now, I'm doing this by:
while 1 {
get_current_temperature;
after 5000;
}
This works, but it prevents the user from interacting with the GUI for the duration of the after command, which is very undesirable. Could anyone suggest a workaround for this?
Thank you for your help!
You are looking for the every proc: https://wiki.tcl-lang.org/page/every
proc every {ms body} {
after $ms [namespace code [info level 0]]
uplevel #0 $body
}
every 5000 get_current_temperature
The every command reschedules itself after the specified number of milliseconds, then runs the command. This is the other way around from most of the versions on the wiki page. It has the advantage(?) that it isn't affected by the time the command takes to run. It also doesn't stop the next iteration if the command throws an error.

How to run repeatedly a proc in Tcl

I have written a proc in tcl which takes one argument (a positive integer) und displays a text. Lets call it permu (for permutation). I would like to execute this proc permanently, so
puts [permu 3]
and with the same argument (here 3), lets say every 2 or 3 seconds or so, without removing the previous outcome of the code. How can I do this?
The second question: Same question as above but I would like to clear the screen when the new outcome of permu is displayed.
Third question: In case that I decide to stop a running code (I work with Linux), for example the one above, how can I do this?
Thanks in advance!
Here's one way to do the repeated output:
proc repeat_permu {arg delay} {
puts [permu $arg]
after $delay [list repeat_permu $arg $delay]
}
# Note that the delay is in milliseconds
repeat_permu 3 3000
# Now start the event loop
vwait forever
To clear the screen you need to send the appropriate escape sequence before each new output. Most terminal emulators will accept the vt100 code, so you would do puts "\x1b[2J".
Normally you could just stop your program running by typing control-c, or do you want some means of doing this programmatically?
Update: A simpler way to do the repetition if you don't need to process any other events in parallel is just: while 1 {puts [permu 3]; after 3000}

TK spinbox goes into infinite cycle of updating GUI

I cannot fix a strange behavior of spinbox. Specifically, I need to update GUI at changing the spinbox's value, by means of -command and update in it.
The code a bit simplified is like:
package require Tk
set sv 1
ttk::spinbox .sp -from 1 -to 9 -textvariable ::sv \
-command {
after 50 ;# some processing imitated
puts [incr ::tmp]:$::sv ;# changes shown in CLI - ok
update ;# changes shown in GUI - ???
}
pack .sp
The problem is when the spinbox's arrow (more "Up" than "Down", but I've not found any regularity in this) is clicked and then pressed 10-20 seconds, the spinbox goes in the infinite cycle of updating, as puts shows.
Of course, the reason is update in the -command code, but I cannot do without it.
Tried in Windows (Tk 8.6.8) and Linux (Tk 8.6.10), ttk::spinbox and spinbox, all revealing the freak.
Is any way to overcome this? Thanks a lot for any help.
In general, don't update the spinbox variable from within the -command callback, and in particular don't run update from within the -command callback. You probably shouldn't do that at all. That command allows processing of events (it runs a subsidiary event loop until the event queue is drained) and is exactly the source of your problems. (I also would suggest not doing update idletasks; that will trigger the reconfigure and redraw that is at the heart of the issue.)
Instead, just stop running the command callback. That returns control to the Tk widget, which will in turn return to the main event loop. You are also advised to not do substantial processing in the callback, and instead to schedule such processing to occur later. Exactly how you do this can be complex, and will definitely be application-specific. One way to move processing later is to just punt it to a procedure that runs in an after event, like this:
package require Tk
set sv 1
proc updateVar {varName} {
upvar "#0" $varName var
after 50; # Processing...
incr var; # Actually update the variable
}
ttk::spinbox .sp -from 1 -to 9 -textvariable ::sv \
-command {after 0 updateVar ::sv}
pack .sp
Note that this does not call update. More substantial postponements of code might involve threads or subprocesses. As I said, getting this right can be complex. It's particularly so when changes to the GUI layout while a mouse button is down cause the selected value to change which in turn causes changes to the GUI layout which …
I made this archive with video to demonstrate the strange behavior of spinbox when update is included in its -command option.
There are two tests in the archive:
test1.tcl presents a way how it should not be done. There are two issues:
the -command code isn't moved to a separate procedure
update is fired immediately from the -command code
The result is seen in test1-spx.mp4: when pressed a spinbox arrow 10-20 seconds, the spinbox goes into an infinite cycle of updating. This behavior is not regular, though well revealed when the focus is switched to another application.
test2.tcl presents a way how this freak can be overcome. The after idle is used to postpone the updating. You can use also after 0 for this.
In a "real test" test2_pave.tcl I use the following procedure for the -command:
proc fontszCheck {} {
lappend ::afters [after 0 {
foreach a $::afters {after cancel $a}
set ::afters [list]
::t::toolBut 4 -3
}]
}
Hopefully, this information would be useful at dealing with Tk spinbox.

Fileevent executed before proc finishes

i have the following code ...
lassign [ chan pipe ] chan chanW
fileevent $chan readable [ list echo $chan ]
proc echo { chan } {
...
}
proc exec { var1 var2 } {
....
puts $chanW "Some output"
....
}
Now according to man fileevent will be executed when the programs idles
is it possible to forse fileevent to be executed before that. For instance is it possible to force the fileevent to be executed immediately after the channel becomes readable, to somehow give it priority .... without using threads :)
Tcl never executes an event handler at “unexpected” points; it only runs them at points where it is asked to do so explicitly or, in some configurations (such as inside wish) when it is doing nothing else. You can introduce an explicit wait for events via two commands:
update
vwait
The update command clears down the current event queue, but does not wait for incoming events (strictly, it does an OS-level wait of length zero). The vwait command will also allow true waiting to happen, waiting until a named Tcl global variable has been written to. (It uses a C-level variable trace to do this, BTW.) Doing either of these will let your code process events before returning. Note that there are a number of other wrappers around this functionality; the geturl command in the http package (in “synchronous” mode) and the tkwait command in the Tk package both do this.
The complication? It's very easy to make your code reenter itself by accident while running the event loop. This can easily end up with you making lots of nested event loop calls, running you out of stack space; don't do that. Instead, prepare for reentrancy issues (a global variable check is on of the easiest approaches to do that) so that you don't nest event loops.
Alternatively, if you're using Tcl 8.6 you can switch your code around to use coroutines. They let you stop the current procedure evaluation and return to the main event loop to wait for a future event before starting execution again: you end up with code that returns at the expected time, but which was suspended for a while first. If you want more information about this approach, please ask another separate question here.

Interrupting a while loop by a variable, then using vwait to wait for the loop to die. Not Working?

I currently have a GUI, that after some automation (using expect) allows the user to interact with one of 10 telnet'ed connections. Interaction is done using the following loop:
#After selecting an item from the menu, this allows the user to interact with that process
proc processInteraction {whichVariable id id_list user_id} {
if {$whichVariable == 1} {
global firstDead
set killInteract $firstDead
} elseif {$whichVariable == 2} {
global secondDead
set killInteract $secondDead
}
global killed
set totalOutput ""
set outputText ""
#set killInteract 0
while {$killInteract == 0} {
set initialTrue 0
if {$whichVariable == 1} {
global firstDead
set killInteract $firstDead
} elseif {$whichVariable == 2} {
global secondDead
set killInteract $secondDead
}
puts "$id: $killInteract"
set spawn_id [lindex $id_list $id]
global global_outfile
interact {
-i $spawn_id
eof {
set outputText "\nProcess closed.\n"
lset deadList $id 1
puts $outputText
#disable the button
disableOption $id $numlcp
break
}
-re (.+) {
set outputText $interact_out(0,string)
append totalOutput $outputText
#-- never looks at the following string as a flag
send_user -- $outputText
#puts $killInteract
continue
}
timeout 1 {
puts "CONTINUE"
continue
}
}
}
puts "OUTSIDE"
if {$killInteract} {
puts "really killed in $id"
set killed 1
}
}
When a new process is selected, the previous should be killed. I previously had it where if a button is clicked, it just enters this loop again. Eventually I realized that the while loops were never quitting, and after 124 button presses, it crashes (stackoverflow =P). They aren't running in the background, but they are on the stack. So I needed a way to kill the loop in the processInteraction function when a new process is started. Here is my last attempt at a solution after many failures:
proc killInteractions {} {
#global killed
global killInteract
global first
global firstDead
global secondDead
global lastAssigned
#First interaction
if {$lastAssigned == 0} {
set firstDead 0
set secondDead 1
set lastAssigned 1
#firstDead was assigned last, kill the first process
} elseif {$lastAssigned == 1} {
set firstDead 1
set secondDead 0
set lastAssigned 2
vwait killed
#secondDead was assigned last, kill the second process
} elseif {$lastAssigned == 2} {
set secondDead 1
set firstDead 0
set lastAssigned 1
vwait killed
}
return $lastAssigned
}
killInteractions is called when a button is pressed. The script hangs on vwait. I know the code seems a bit odd/wonky for handling processes with two variables, but this was a desperate last ditch effort to get this to work.
A dead signal is sent to the correct process (in the form of secondDead or firstDead). I have the timeout value set at 1 second for the interact, so that it is forced to keep checking if the while loop is true, even while the user is interacting with that telnet'ed session. Once the dead signal is sent, it waits for confirmation that the process has died (through vwait).
The issue is that once the signal is sent, the loop will never realize it should die unless it is given the context to check it. The loop needs to run until it is kicked out by first or secondDead. So there needs to be some form of wait before switching to the next process, allowing the loop in processInteraction of the previous process to have control.
Any help would be greatly appreciated.
Your code seems extremely complicated to me. However, the key problem is that you are running inner event loops (the event loop code is pretty simple-minded, and so is predictably a problem) and building up the C stack with things that are stuck. You don't want that!
Let's start by identifying where those inner event loops are. Firstly, vwait is one of the canonical event loop commands; it runs an event loop until its variable is set (by an event script, presumably). However, it is not the only one. In particular, Expect's interact also runs an event loop. This means that everything can become nested and tangled and… well, you don't want that. (That page talks about update, but it applies to all nested event looping.) Putting an event loop inside your own while is particularly likely to lead to debugging headaches.
The best route to fixing this is to rewrite the code to use continuation-passing style. Instead of writing code with nested event loops, you instead rearrange things so that you have pieces of code that are evaluated on events and which pass such state as is necessary between them without starting a nested event loop. (If you weren't using Expect and were using Tcl 8.6, I'd advise using coroutine to do this, but I don't think that works with Expect currently and it does require a beta version of Tcl that isn't widely deployed yet.)
Alas, everything is made more complicated by the need to interact with the subprocesses. There's no way to interact in the background (nor does it really make that much sense). What you instead need to do is to use exactly one interact in your whole program and to have it switch between spawned connections. You do that by giving the -i option the name of a global variable which holds the current id to interact with, instead of the id directly. (This is an “indirect” spawn id.) I think that the easiest way of making this work is to have a “not connected to anything else” spawn id (e.g., you connect it to cat >/dev/null just to act as a do-nothing) that you make at the start of your script, and then swap in the real connection when it makes sense. The actual things that you currently use interact to watch out for are best done with expect_background (remember to use expect_out instead of interact_out).
Your code is rather too long for me to rewrite, but what you should do is to look very carefully at the logic of the eof clause of the interact; it needs to do more than it does at the moment. The code to kill from the GUI should be changed too; it should send a suitable EOF marker to the spawned process(es) to be killed and not wait for the death to be confirmed.