File: tk/timer.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  class: TkTimer#8
extends
  TkCore   
includes
  TkCore   
inherits from
  Object ( Builtin-Module )
has properties
constant: TkCommandNames #12
constant: Tk_CBID #14
method: mutex #16
constant: Tk_CBTBL #20
constant: DEFAULT_IGNORE_EXCEPTIONS #30
class method: start (1/2) / 2 #35
class method: callback / 1 #39
class method: info (1/2) / 1 #45
method: do_callback #69
method: set_callback / 2 #98
method: set_next_callback / 1 #110
method: initialize / 2 #147
attribute: after_id [R] #207
attribute: after_script [R] #208
attribute: current_proc [R] #209
attribute: current_args [R] #210
attribute: current_sleep [R] #211
alias: current_interval current_sleep #212
attribute: return_value [R] #213
attribute: loop_exec [RW] #215
method: __at_end__ #217
method: cb_call #223
method: get_procs #227
method: current_status #231
method: cancel_on_exception? #236
method: cancel_on_exception= / 1 #240
method: running? #251
method: loop_rest #255
method: loop_rest= / 1 #259
method: set_interval / 1 #264
method: set_procs / 3 #274
method: add_procs / 1 #312
method: delete_procs / 1 #326
method: delete_at / 1 #342
method: set_start_proc / 4 #349
method: start (2/E) / 2 #367
method: reset / 1 #411
method: restart / 2 #429
method: cancel #438
alias: stop cancel #448
method: continue / 1 #450
method: skip #467
method: info (2/E) #476
method: at_end / 2 #485
method: wait / 2 #500
method: eventloop_wait / 1 #520
method: thread_wait / 1 #523
method: tkwait / 1 #526
method: eventloop_tkwait #529
method: thread_tkwait #532
  class: TkRTTimer#540
inherits from
  TkTimer   
has properties
constant: DEFAULT_OFFSET_LIST_SIZE #541
method: initialize / 2 #543
method: start / 2 #552
method: cancel #559
alias: stop cancel #565
method: continue / 1 #567
method: set_interval / 1 #573
method: _offset_ave #578
method: set_next_callback / 1 #594
method: cb_call #648

Class Hierarchy

Object ( Builtin-Module )
TkTimer#8
  TkRTTimer    #540

Code

   1  #
   2  #   tk/timer.rb : methods for Tcl/Tk after command
   3  #
   4  #   $Id: timer.rb 16020 2008-04-14 15:17:52Z nagai $
   5  #
   6  require 'tk'
   7 
   8  class TkTimer
   9    include TkCore
  10    extend TkCore
  11 
  12    TkCommandNames = ['after'.freeze].freeze
  13 
  14    (Tk_CBID = ['a'.freeze, '00000'.taint]).instance_eval{
  15      @mutex = Mutex.new
  16      def mutex; @mutex; end
  17      freeze
  18    }
  19 
  20    Tk_CBTBL = {}.taint
  21 
  22    TkCore::INTERP.add_tk_procs('rb_after', 'id', <<-'EOL')
  23      if {[set st [catch {eval {ruby_cmd TkTimer callback} $id} ret]] != 0} {
  24          return -code $st $ret
  25      } {
  26          return $ret
  27      }
  28    EOL
  29 
  30    DEFAULT_IGNORE_EXCEPTIONS = [ NameError, RuntimeError ].freeze
  31 
  32    ###############################
  33    # class methods
  34    ###############################
  35    def self.start(*args, &b)
  36      self.new(*args, &b).start
  37    end
  38 
  39    def self.callback(obj_id)
  40      ex_obj = Tk_CBTBL[obj_id]
  41      return "" if ex_obj == nil; # canceled
  42      ex_obj.cb_call
  43    end
  44 
  45    def self.info(obj = nil)
  46      if obj
  47        if obj.kind_of?(TkTimer)
  48          if obj.after_id
  49            inf = tk_split_list(tk_call_without_enc('after','info',obj.after_id))
  50            [Tk_CBTBL[inf[0][1]], inf[1]]
  51          else
  52            nil
  53          end
  54        else
  55          fail ArgumentError, "TkTimer object is expected"
  56        end
  57      else
  58        tk_call_without_enc('after', 'info').split(' ').collect!{|id|
  59          ret = Tk_CBTBL.find{|key,val| val.after_id == id}
  60          (ret == nil)? id: ret[1]
  61        }
  62      end
  63    end
  64 
  65 
  66    ###############################
  67    # instance methods
  68    ###############################
  69    def do_callback
  70      @in_callback = true
  71      @after_id = nil
  72      begin
  73        @return_value = @current_proc.call(self)
  74      rescue SystemExit
  75        exit(0)
  76      rescue Interrupt
  77        exit!(1)
  78      rescue Exception => e
  79        if @cancel_on_exception && 
  80            @cancel_on_exception.find{|exc| e.kind_of?(exc)}
  81          cancel
  82          @return_value = e
  83          @in_callback = false
  84          return e
  85        else
  86          fail e
  87        end
  88      end
  89      if @set_next
  90        set_next_callback(@current_args)
  91      else
  92        @set_next = true
  93      end
  94      @in_callback = false
  95      @return_value
  96    end
  97 
  98    def set_callback(sleep, args=nil)
  99      if TkCore::INTERP.deleted?
 100        self.cancel
 101        return self
 102      end
 103      @after_script = "rb_after #{@id}"
 104      @current_args = args
 105      @current_script = [sleep, @after_script]
 106      @after_id = tk_call_without_enc('after', sleep, @after_script)
 107      self
 108    end
 109 
 110    def set_next_callback(args)
 111      if @running == false || @proc_max == 0 || @do_loop == 0
 112        Tk_CBTBL.delete(@id) ;# for GC
 113        @running = false
 114        # @wait_var.value = 0
 115        __at_end__
 116        return
 117      end
 118      if @current_pos >= @proc_max
 119        if @do_loop < 0 || (@do_loop -= 1) > 0
 120          @current_pos = 0
 121        else
 122          Tk_CBTBL.delete(@id) ;# for GC
 123          @running = false
 124          # @wait_var.value = 0
 125          __at_end__
 126          return
 127        end
 128      end
 129 
 130      @current_args = args
 131 
 132      # if @sleep_time.kind_of?(Proc)
 133      if TkComm._callback_entry?(@sleep_time)
 134        sleep = @sleep_time.call(self)
 135      else
 136        sleep = @sleep_time
 137      end
 138      @current_sleep = sleep
 139 
 140      cmd, *cmd_args = @loop_proc[@current_pos]
 141      @current_pos += 1
 142      @current_proc = cmd
 143 
 144      set_callback(sleep, cmd_args)
 145    end
 146 
 147    def initialize(*args, &b)
 148      Tk_CBID.mutex.synchronize{
 149        # @id = Tk_CBID.join('')
 150        @id = Tk_CBID.join(TkCore::INTERP._ip_id_)
 151        Tk_CBID[1].succ!
 152      }
 153 
 154      @wait_var = TkVariable.new(0)
 155 
 156      @at_end_proc = nil
 157 
 158      @cb_cmd = TkCore::INTERP.get_cb_entry(self.method(:do_callback))
 159 
 160      @set_next = true
 161 
 162      @init_sleep = 0
 163      @init_proc = nil
 164      @init_args = []
 165 
 166      @current_script = []
 167      @current_proc = nil
 168      @current_args = nil
 169      @return_value = nil
 170 
 171      @sleep_time = 0
 172      @current_sleep = 0
 173      @loop_exec = 0
 174      @do_loop = 0
 175      @loop_proc = []
 176      @proc_max = 0
 177      @current_pos = 0
 178 
 179      @after_id = nil
 180      @after_script = nil
 181 
 182      @cancel_on_exception = DEFAULT_IGNORE_EXCEPTIONS
 183      # Unless @cancel_on_exception, Ruby/Tk shows an error dialog box when 
 184      # an excepsion is raised on TkTimer callback procedure. 
 185      # If @cancel_on_exception is an array of exception classes and the raised 
 186      # exception is included in the array, Ruby/Tk cancels executing TkTimer 
 187      # callback procedures silently (TkTimer#cancel is called and no dialog is 
 188      # shown). 
 189 
 190      if b
 191        case args.size
 192        when 0
 193          add_procs(b)
 194        when 1
 195          args << -1 << b
 196        else
 197          args << b
 198        end
 199      end
 200 
 201      set_procs(*args) if args != []
 202 
 203      @running = false
 204      @in_callback = false
 205    end
 206 
 207    attr :after_id
 208    attr :after_script
 209    attr :current_proc
 210    attr :current_args
 211    attr :current_sleep
 212    alias :current_interval :current_sleep
 213    attr :return_value
 214 
 215    attr_accessor :loop_exec
 216 
 217    def __at_end__
 218      @at_end_proc.call(self) if @at_end_proc
 219      @wait_var.value = 0  # for wait
 220    end
 221    private :__at_end__
 222 
 223    def cb_call
 224      @cb_cmd.call
 225    end
 226 
 227    def get_procs
 228      [@init_sleep, @init_proc, @init_args, @sleep_time, @loop_exec, @loop_proc]
 229    end
 230 
 231    def current_status
 232      [@running, @current_sleep, @current_proc, @current_args, 
 233        @do_loop, @cancel_on_exception]
 234    end
 235 
 236    def cancel_on_exception?
 237      @cancel_on_exception
 238    end
 239 
 240    def cancel_on_exception=(mode)
 241      if mode.kind_of?(Array)
 242        @cancel_on_exception = mode
 243      elsif mode
 244        @cancel_on_exception = DEFAULT_IGNORE_EXCEPTIONS
 245      else
 246        @cancel_on_exception = false
 247      end
 248      #self
 249    end
 250 
 251    def running?
 252      @running
 253    end
 254 
 255    def loop_rest
 256      @do_loop
 257    end
 258 
 259    def loop_rest=(rest)
 260      @do_loop = rest
 261      #self
 262    end
 263 
 264    def set_interval(interval)
 265      #if interval != 'idle' && interval != :idle \
 266      #  && !interval.kind_of?(Integer) && !interval.kind_of?(Proc)
 267      if interval != 'idle' && interval != :idle \
 268        && !interval.kind_of?(Integer) && !TkComm._callback_entry?(interval)
 269        fail ArgumentError, "expect Integer or Proc"
 270      end
 271      @sleep_time = interval
 272    end
 273 
 274    def set_procs(interval, loop_exec, *procs)
 275      #if interval != 'idle' && interval != :idle \
 276      #   && !interval.kind_of?(Integer) && !interval.kind_of?(Proc)
 277      if interval != 'idle' && interval != :idle \
 278        && !interval.kind_of?(Integer) && !TkComm._callback_entry?(interval)
 279        fail ArgumentError, "expect Integer or Proc for 1st argument"
 280      end
 281      @sleep_time = interval
 282 
 283      @loop_proc = []
 284      procs.each{|e|
 285        # if e.kind_of?(Proc)
 286        if TkComm._callback_entry?(e)
 287          @loop_proc.push([e])
 288        else
 289          @loop_proc.push(e)
 290        end
 291      }
 292      @proc_max = @loop_proc.size
 293      @current_pos = 0
 294 
 295      if loop_exec.kind_of?(Integer) && loop_exec < 0
 296        @loop_exec = -1
 297      elsif loop_exec == true
 298        @loop_exec = -1
 299      elsif loop_exec == nil || loop_exec == false || loop_exec == 0
 300        @loop_exec = 0
 301      else
 302        if not loop_exec.kind_of?(Integer)
 303          fail ArgumentError, "expect Integer for 2nd argument"
 304        end
 305        @loop_exec = loop_exec
 306      end
 307      @do_loop = @loop_exec
 308 
 309      self
 310    end
 311 
 312    def add_procs(*procs)
 313      procs.each{|e|
 314        # if e.kind_of?(Proc)
 315        if TkComm._callback_entry?(e)
 316          @loop_proc.push([e])
 317        else
 318          @loop_proc.push(e)
 319        end
 320      }
 321      @proc_max = @loop_proc.size
 322 
 323      self
 324    end
 325 
 326    def delete_procs(*procs)
 327      procs.each{|e|
 328        # if e.kind_of?(Proc)
 329        if TkComm._callback_entry?(e)
 330          @loop_proc.delete([e])
 331        else
 332          @loop_proc.delete(e)
 333        end
 334      }
 335      @proc_max = @loop_proc.size
 336 
 337      cancel if @proc_max == 0
 338 
 339      self
 340    end
 341 
 342    def delete_at(n)
 343      @loop_proc.delete_at(n)
 344      @proc_max = @loop_proc.size
 345      cancel if @proc_max == 0
 346      self
 347    end
 348 
 349    def set_start_proc(sleep=nil, init_proc=nil, *init_args, &b)
 350      # set parameters for 'restart'
 351      sleep = @init_sleep unless sleep
 352 
 353      if sleep != 'idle' && sleep != :idle && !sleep.kind_of?(Integer)
 354        fail ArgumentError, "expect Integer or 'idle' for 1st argument"
 355      end
 356 
 357      @init_sleep = sleep
 358      @init_proc = init_proc
 359      @init_args = init_args
 360 
 361      @init_proc = b if !@init_proc && b
 362      @init_proc = proc{|*args| } if @init_sleep > 0 && !@init_proc
 363 
 364      self
 365    end
 366 
 367    def start(*init_args, &b)
 368      return nil if @running
 369 
 370      Tk_CBTBL[@id] = self
 371      @do_loop = @loop_exec
 372      @current_pos = 0
 373      @return_value = nil
 374      @after_id = nil
 375 
 376      @init_sleep = 0
 377      @init_proc  = nil
 378      @init_args  = nil
 379 
 380      argc = init_args.size
 381      if argc > 0
 382        sleep = init_args.shift
 383        if sleep != 'idle' && sleep != :idle && !sleep.kind_of?(Integer)
 384          fail ArgumentError, "expect Integer or 'idle' for 1st argument"
 385        end
 386        @init_sleep = sleep
 387      end
 388      @init_proc = init_args.shift if argc > 1
 389      @init_args = init_args if argc > 2
 390 
 391      @init_proc = b if !@init_proc && b
 392      @init_proc = proc{|*args| } if @init_sleep > 0 && !@init_proc
 393 
 394      @current_sleep = @init_sleep
 395      @running = true
 396      if @init_proc
 397        # if not @init_proc.kind_of?(Proc)
 398        if !TkComm._callback_entry?(@init_proc)
 399          fail ArgumentError, "Argument '#{@init_proc}' need to be Proc"
 400        end
 401        @current_proc = @init_proc
 402        set_callback(@init_sleep, @init_args)
 403        @set_next = false if @in_callback
 404      else
 405        set_next_callback(@init_args)
 406      end
 407 
 408      self
 409    end
 410 
 411    def reset(*reset_args)
 412      restart() if @running
 413 
 414      if @init_proc
 415        @return_value = @init_proc.call(self)
 416      else
 417        @return_value = nil
 418      end
 419 
 420      @current_pos   = 0
 421      @current_args  = @init_args
 422      @current_script = []
 423 
 424      @set_next = false if @in_callback
 425 
 426      self
 427    end
 428 
 429    def restart(*restart_args, &b)
 430      cancel if @running
 431      if restart_args == [] && !b
 432        start(@init_sleep, @init_proc, *@init_args)
 433      else
 434        start(*restart_args, &b)
 435      end
 436    end
 437 
 438    def cancel
 439      @running = false
 440      # @wait_var.value = 0
 441      __at_end__
 442      tk_call 'after', 'cancel', @after_id if @after_id
 443      @after_id = nil
 444 
 445      Tk_CBTBL.delete(@id) ;# for GC
 446      self
 447    end
 448    alias stop cancel
 449 
 450    def continue(wait=nil)
 451      fail RuntimeError, "is already running" if @running
 452      return restart() if @current_script.empty?
 453      sleep, cmd = @current_script
 454      fail RuntimeError, "no procedure to continue" unless cmd
 455      if wait
 456        unless wait.kind_of?(Integer)
 457          fail ArgumentError, "expect Integer for 1st argument"
 458        end
 459        sleep = wait
 460      end
 461      Tk_CBTBL[@id] = self
 462      @running = true
 463      @after_id = tk_call_without_enc('after', sleep, cmd)
 464      self
 465    end
 466 
 467    def skip
 468      fail RuntimeError, "is not running now" unless @running
 469      cancel
 470      Tk_CBTBL[@id] = self
 471      @running = true
 472      set_next_callback(@current_args)
 473      self
 474    end
 475 
 476    def info
 477      if @after_id
 478        inf = tk_split_list(tk_call_without_enc('after', 'info', @after_id))
 479        [Tk_CBTBL[inf[0][1]], inf[1]]
 480      else
 481        nil
 482      end
 483    end
 484 
 485    def at_end(*arg, &b)
 486      if arg.empty?
 487        if b 
 488          @at_end_proc = b
 489        else 
 490          # no proc
 491          return @at_end_proc 
 492        end
 493      else
 494        fail ArgumentError, "wrong number of arguments" if arg.length != 1 || b
 495        @at_end_proc = arg[0]
 496      end
 497      self
 498    end
 499 
 500    def wait(on_thread = true, check_root = false)
 501      if $SAFE >= 4
 502        fail SecurityError, "can't wait timer at $SAFE >= 4"
 503      end
 504 
 505      unless @running
 506        if @return_value.kind_of?(Exception)
 507          fail @return_value 
 508        else
 509          return @return_value 
 510        end
 511      end
 512 
 513      @wait_var.wait(on_thread, check_root)
 514      if @return_value.kind_of?(Exception)
 515        fail @return_value 
 516      else
 517        @return_value 
 518      end
 519    end
 520    def eventloop_wait(check_root = false)
 521      wait(false, check_root)
 522    end
 523    def thread_wait(check_root = false)
 524      wait(true, check_root)
 525    end
 526    def tkwait(on_thread = true)
 527      wait(on_thread, true)
 528    end
 529    def eventloop_tkwait
 530      wait(false, true)
 531    end
 532    def thread_tkwait
 533      wait(true, true)
 534    end
 535  end
 536 
 537  TkAfter = TkTimer
 538 
 539 
 540  class TkRTTimer < TkTimer
 541    DEFAULT_OFFSET_LIST_SIZE = 5
 542 
 543    def initialize(*args, &b)
 544      super(*args, &b)
 545 
 546      @offset_list = Array.new(DEFAULT_OFFSET_LIST_SIZE){ [0, 0] }
 547      @offset_s = 0
 548      @offset_u = 0
 549      @est_time = nil
 550    end
 551 
 552    def start(*args, &b)
 553      return nil if @running
 554      @est_time = nil
 555      @cb_start_time = Time.now
 556      super(*args, &b)
 557    end
 558 
 559    def cancel
 560      super()
 561      @est_time = nil
 562      @cb_start_time = Time.now
 563      self
 564    end
 565    alias stop cancel
 566 
 567    def continue(wait=nil)
 568      fail RuntimeError, "is already running" if @running
 569      @cb_start_time = Time.now
 570      super(wait)
 571    end
 572 
 573    def set_interval(interval)
 574      super(interval)
 575      @est_time = nil
 576    end
 577 
 578    def _offset_ave
 579      size = 0
 580      d_sec = 0; d_usec = 0
 581      @offset_list.each_with_index{|offset, idx|
 582        # weight = 1
 583        weight = idx + 1
 584        size += weight
 585        d_sec += offset[0] * weight
 586        d_usec += offset[1] * weight
 587      }
 588      offset_s, mod = d_sec.divmod(size)
 589      offset_u = ((mod * 1000000 + d_usec) / size.to_f).round
 590      [offset_s, offset_u]
 591    end
 592    private :_offset_ave
 593 
 594    def set_next_callback(args)
 595      if @running == false || @proc_max == 0 || @do_loop == 0
 596        Tk_CBTBL.delete(@id) ;# for GC
 597        @running = false
 598        # @wait_var.value = 0
 599        __at_end__
 600        return
 601      end
 602      if @current_pos >= @proc_max
 603        if @do_loop < 0 || (@do_loop -= 1) > 0
 604          @current_pos = 0
 605        else
 606          Tk_CBTBL.delete(@id) ;# for GC
 607          @running = false
 608          # @wait_var.value = 0
 609          __at_end__
 610          return
 611        end
 612      end
 613 
 614      @current_args = args
 615 
 616      cmd, *cmd_args = @loop_proc[@current_pos]
 617      @current_pos += 1
 618      @current_proc = cmd
 619 
 620      @offset_s, @offset_u = _offset_ave
 621 
 622      if TkComm._callback_entry?(@sleep_time)
 623        sleep = @sleep_time.call(self)
 624      else
 625        sleep = @sleep_time
 626      end
 627 
 628      if @est_time
 629        @est_time = Time.at(@est_time.to_i, @est_time.usec + sleep*1000)
 630      else
 631        @est_time = Time.at(@cb_start_time.to_i, 
 632                            @cb_start_time.usec + sleep*1000)
 633      end
 634 
 635      now = Time.now
 636      real_sleep = ((@est_time.to_i - now.to_i + @offset_s)*1000.0 + 
 637                    (@est_time.usec - now.usec + @offset_u)/1000.0).round
 638      if real_sleep <= 0
 639        real_sleep = 0
 640        @offset_s = now.to_i
 641        @offset_u = now.usec
 642      end
 643      @current_sleep = real_sleep
 644 
 645      set_callback(real_sleep, cmd_args)
 646    end
 647 
 648    def cb_call
 649      if @est_time
 650        @offset_list.shift
 651 
 652        @cb_start_time = Time.now
 653 
 654        if @current_sleep == 0
 655          @offset_list.push([
 656                              @offset_s - @cb_start_time.to_i, 
 657                              @offset_u - @cb_start_time.usec
 658                            ])
 659        else
 660          @offset_list.push([
 661                              @offset_s + (@est_time.to_i - @cb_start_time.to_i),
 662                              @offset_u + (@est_time.usec - @cb_start_time.usec)
 663                            ])
 664        end
 665      end
 666 
 667      @cb_cmd.call
 668    end
 669  end