ruby/tk and loop delay

E

Ed Redman

I am writing a card game in ruby/tk.

I have a Tk_button(shuffle) that shuffles the cards and places them on a
canvas.The shuffle works fine but I would like to have a slight delay
between between each card being displayed on the canvas. Below is the
shuffle buttons command. I would like to delay the loop each time so
their is a slight delay. I have tried putting Tk.after(time)
{ part around the loop} but it just delays the shuffle and is not
visible. Not each card being displayed. I need a little direction
on implementing such a loop

thanks



shuffle.command do
52.times do
|i|

nr = rand(56)
nm = rand(56)
io = newhash[nr]
im = newhash[nm]
# Tk.after(100) {
place[nr].image io
newhash[nm] = io
place[nr].image im
newhash[nr] = im
# }
end

end
 
7

7stud --

Ed said:
I am writing a card game in ruby/tk.

I have a Tk_button(shuffle) that shuffles the cards and places them on a
canvas.The shuffle works fine but I would like to have a slight delay
between between each card being displayed on the canvas. Below is the
shuffle buttons command. I would like to delay the loop each time so
their is a slight delay. I have tried putting Tk.after(time)
{ part around the loop} but it just delays the shuffle and is not
visible. Not each card being displayed. I need a little direction
on implementing such a loop

thanks



shuffle.command do
52.times do
|i|

nr = rand(56)
nm = rand(56)
io = newhash[nr]
im = newhash[nm]
# Tk.after(100) {
place[nr].image io
newhash[nm] = io
place[nr].image im
newhash[nr] = im
# }
end

end

Are you not supposed to use sleep() for some reason? This works for me:

require 'tk'

def do_press(e)
widget = e.widget

x = e.x
y = e.y

10.times do
x += 10
TkcLine.new(widget, x, y, x+100, y+100)
widget.update
sleep(2)
end

end


root = TkRoot.new {title 'Canvas Test'}
canvas = TkCanvas.new(root)
canvas.pack

canvas.bind('ButtonRelease-1', lambda {|e| do_press(e)})
Tk.mainloop
 
H

Hidetoshi NAGAI

From: Ed Redman <[email protected]>
Subject: ruby/tk and loop delay
Date: Mon, 14 Apr 2008 08:20:05 +0900
Message-ID: said:
I have a Tk_button(shuffle) that shuffles the cards and places them on a
canvas.The shuffle works fine but I would like to have a slight delay
between between each card being displayed on the canvas. Below is the
shuffle buttons command. I would like to delay the loop each time so
their is a slight delay. I have tried putting Tk.after(time)
{ part around the loop} but it just delays the shuffle and is not
visible. Not each card being displayed. I need a little direction
on implementing such a loop

On Tk, re-drawing widgets occurs when idle-tasks.
The callback operation for button click is only one event.
Tk doesn't call idle loop before finishing of the callback.
If you want to force idle loop, please call "Tk.update" or
"Tk.update_idletasks" at when you need.

However, I don't recommend such long term operation on one callback.
Such callbacks may block callbacks for other events.
That is, you may get bad response of GUI.

I think that Thread + TkTimer is better for your case.
----------------------------------------------------------
proc exchange{|nr, nm|
newhash[nr], newhasn[nm] = newhash[nm], newhasn[nr]
place[nr].image newhash[nr]
place[nm].image newhash[nm]
# Tk.update ### if you need
}

# 100ms interval, 52 times
tm = TkTimer.new(100, 52){ exchange.call(rand(56), rand(56)) }

shuffle.command{
shuffle.state:)disable)
Thread.new{ tm.start.wait; shuffle.state:)normal) }
}
----------------------------------------------------------
On this code, the callback for clicking 'shuffle' button
will finish in short term.
And GUI can treat the next event soon, although 'shuffle' button
is disabled.
The thread created on the callback starts and waits the timer,
and enable 'shuffle' button when the timer is finished.

# I should add the method to entry a proc executed at end of a timer.
# Then, a thread isn't required for such operataion.
 
7

7stud --

Try using the calling the update() method on the widget. I can't get
the after() method to work at all: even if I specify 40,000,000
milliseconds as the delay, all the lines get drawn instantly.

require 'tk'

def do_press(e)
widget = e.widget
x = e.x
y = e.y

threads = []

10.times do |i|
x += 10
TkcLine.new(widget, x, y, x+100, y+100)

Tk.after(40000000, proc{widget.update} )
end

end


root = TkRoot.new {title 'Canvas Test'}
canvas = TkCanvas.new(root)
canvas.pack

canvas.bind('ButtonRelease-1', lambda {|e| do_press(e)})
Tk.mainloop
 
7

7stud --

Hidetoshi said:
From: Ed Redman <[email protected]>
Subject: ruby/tk and loop delay
Date: Mon, 14 Apr 2008 08:20:05 +0900
Message-ID: said:
I have a Tk_button(shuffle) that shuffles the cards and places them on a
canvas.The shuffle works fine but I would like to have a slight delay
between between each card being displayed on the canvas. Below is the
shuffle buttons command. I would like to delay the loop each time so
their is a slight delay. I have tried putting Tk.after(time)
{ part around the loop} but it just delays the shuffle and is not
visible. Not each card being displayed. I need a little direction
on implementing such a loop

On Tk, re-drawing widgets occurs when idle-tasks.
The callback operation for button click is only one event.
Tk doesn't call idle loop before finishing of the callback.
If you want to force idle loop, please call "Tk.update" or
"Tk.update_idletasks" at when you need.

However, I don't recommend such long term operation on one callback.
Such callbacks may block callbacks for other events.
That is, you may get bad response of GUI.

I think that Thread + TkTimer is better for your case.
----------------------------------------------------------
proc exchange{|nr, nm|
newhash[nr], newhasn[nm] = newhash[nm], newhasn[nr]
place[nr].image newhash[nr]
place[nm].image newhash[nm]
# Tk.update ### if you need
}

# 100ms interval, 52 times
tm = TkTimer.new(100, 52){ exchange.call(rand(56), rand(56)) }

shuffle.command{
shuffle.state:)disable)
Thread.new{ tm.start.wait; shuffle.state:)normal) }
}
----------------------------------------------------------
On this code, the callback for clicking 'shuffle' button
will finish in short term.
And GUI can treat the next event soon, although 'shuffle' button
is disabled.
The thread created on the callback starts and waits the timer,
and enable 'shuffle' button when the timer is finished.

# I should add the method to entry a proc executed at end of a timer.
# Then, a thread isn't required for such operataion.


Isn't after() supposed to accomplish the same thing? Why doesn't
after() work in my code in my previous post?

I tried my own threading solution, but it didn't work--there was no
delay when drawing lines to a Canvas widget:

require 'tk'

def do_press(e)
widget = e.widget
x = e.x
y = e.y


10.times do |i|
x += 10
TkcLine.new(widget, x, y, x+100, y+100)

t = Thread.new do
sleep(2)
widget.update
end

end
end



root = TkRoot.new {title 'Canvas Test'}
canvas = TkCanvas.new(root)
canvas.pack

canvas.bind('ButtonRelease-1', lambda {|e| do_press(e)})
Tk.mainloop
 
H

Hidetoshi NAGAI

From: 7stud -- <[email protected]>
Subject: Re: ruby/tk and loop delay
Date: Mon, 14 Apr 2008 19:22:40 +0900
Message-ID: said:
Isn't after() supposed to accomplish the same thing? Why doesn't
after() work in my code in my previous post?

Probably, you misunderstand about 'update'.
It is the command to force to flush the event queue.

First, 'update' executes each of the standard evenst (window-event,
timer-event, file-event, and so on) on the event queue.
Next, 'update' executes each of the idle tasks on the queue.
After those (the event queue is empty), 'update' finishes and returns.

'update_idletasks' doesn't executes standard events.
It executes idle tasks only.

'update' and 'update_idletasks' are used to avoid the trouble
about the conflict between current status and planned status
of a widget.

Each widget, when needs re-drawing, registers an idle task
(for re-drawing) to the event queue.
Idle tasks are called by the eventloop, when no standard event
exists on the event queue.

So,
10.times do |i|
x += 10
TkcLine.new(widget, x, y, x+100, y+100)

t = Thread.new do
sleep(2)
widget.update
end

end

you cannot delay drawing widgets by 'update' or 'update_idletasks'.
Even if you replace 'widget.update' to 'TkcLine.new(...)',
your code will write 10 lines at much the same time,
because those 10 threads starts at much the same time.
If you need 2 seconds of intervel, you must set the next callback
in each callback of after().

TkTimer objects do that. For example,
---------------------------------------------------------------------
TkTimer.new(2000, 10){|tm|
x, y = tm.return_value # <= return value of the previous callback
x += 10
TkcLine.new(widget, x, y, x+100, y+100)
[x, y] # => passed to the next callback
}.start{ [50, 50] } # <= initail proc: return value is passed to the
# first callback
---------------------------------------------------------------------
However, the intervals are not accurate.
Tk doesn't have RealTime monitor, so the time of callback execution
and the timing of starting callbacks are the reason of why.

TkRTTimer objects do error correction about intervals.
The intervals are not accurate, too.
But TkRTTimer objects keep difference between summation of the
intervals and realtime small.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top