Class: Processing::Watcher

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby-processing/runners/watch.rb

Overview

A sketch loader, observer, and reloader, to tighten the feedback between code and effect.

Instance Method Summary collapse

Constructor Details

#initializeWatcher

Sic a new Processing::Watcher on the sketch



10
11
12
13
14
15
16
# File 'lib/ruby-processing/runners/watch.rb', line 10

def initialize
  @file = SKETCH_PATH
  @time = Time.now
  # Doesn't work well enough for now.
  # record_state_of_ruby
  start_watching
end

Instance Method Details

#record_state_of_rubyObject

Do the best we can to take a picture of the current Ruby interpreter. For now, this means top-level constants and loaded .rb files.



74
75
76
77
78
79
# File 'lib/ruby-processing/runners/watch.rb', line 74

def record_state_of_ruby
  @saved_constants  = Object.send(:constants).dup
  @saved_load_paths = $LOAD_PATH.dup
  @saved_features   = $LOADED_FEATURES.dup
  @saved_globals    = Kernel.global_variables.dup
end

#recursively_remove_constants(base, constant_names) ⇒ Object

Used to clean up declared constants in code that needs to be reloaded.



103
104
105
106
107
108
109
110
111
112
# File 'lib/ruby-processing/runners/watch.rb', line 103

def recursively_remove_constants(base, constant_names)
  constants = constant_names.map {|name| base.const_get(name) }
  constants.each_with_index do |c, i|
    java_obj = Java::JavaLang::Object
    constants[i] = constant_names[i] = nil if c.respond_to?(:ancestors) && c.ancestors.include?(java_obj)
    constants[i] = nil if !c.is_a?(Class) && !c.is_a?(Module)
  end
  constants.each {|c| recursively_remove_constants(c, c.constants) if c }
  constant_names.each {|name| base.send(:remove_const, name.to_sym) if name }
end

#report_errorsObject

Convenience function to report errors when loading and running a sketch, instead of having them eaten by the thread they are loaded in.



42
43
44
45
46
47
48
# File 'lib/ruby-processing/runners/watch.rb', line 42

def report_errors
  yield
rescue Exception => e
  puts "Exception occured while running sketch #{File.basename SKETCH_PATH}:"
  puts e.to_s
  puts e.backtrace.join("\n")
end

#rewind_to_recorded_stateObject

Try to go back to the recorded Ruby state.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ruby-processing/runners/watch.rb', line 83

def rewind_to_recorded_state
  new_constants  = Object.send(:constants).reject {|c| @saved_constants.include?(c) }
  new_load_paths = $LOAD_PATH.reject {|p| @saved_load_paths.include?(p) }
  new_features   = $LOADED_FEATURES.reject {|f| @saved_features.include?(f) }
  new_globals    = Kernel.global_variables.reject {|g| @saved_globals.include?(g) }

  Processing::App.recursively_remove_constants(Object, new_constants)
  new_load_paths.each {|p| $LOAD_PATH.delete(p) }
  new_features.each {|f| $LOADED_FEATURES.delete(f) }
  new_globals.each do |g|
    begin
      eval("#{g} = nil") # There's no way to undef a global variable in Ruby
    rescue NameError => e
      # Some globals are read-only, and we can't set them to nil.
    end
  end
end

#start_watchingObject

Kicks off a thread to watch the sketch, reloading Ruby-Processing and restarting the sketch whenever it changes.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ruby-processing/runners/watch.rb', line 21

def start_watching
  @runner = Thread.start { report_errors { Processing.load_and_run_sketch } } unless $app
  thread = Thread.start do
    loop do
      file_mtime = File.stat(@file).mtime
      if file_mtime > @time
        @time = file_mtime
        wipe_out_current_app!
        # Taking it out the reset until it can be made to work more reliably
        # rewind_to_recorded_state
        GC.start
        @runner = Thread.start { report_errors { Processing.load_and_run_sketch } }
      end
      sleep 0.33
    end
  end
  thread.join
end

#wipe_out_current_app!Object

Used to completely remove all traces of the current sketch, so that it can be loaded afresh. Go down into modules to find it, even.



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ruby-processing/runners/watch.rb', line 52

def wipe_out_current_app!
  @runner.kill if @runner.alive?
  app = $app
  return unless app
  app.no_loop
  # Wait for the animation thread to finish rendering
  sleep 0.075
  app.close
  constant_names = app.class.to_s.split(/::/)
  app_class_name = constant_names.pop
  obj = constant_names.inject(Object) {|o, name| o.send(:const_get, name) }
  obj.send(:remove_const, app_class_name)
end