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
17
# File 'lib/ruby-processing/runners/watch.rb', line 10

def initialize
  @file = Processing::SKETCH_PATH
  @time = Time.now
  # Doesn't work well enough for now.
  # record_state_of_ruby
  Processing.load_and_run_sketch unless $app
  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.



68
69
70
71
72
73
# File 'lib/ruby-processing/runners/watch.rb', line 68

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.



97
98
99
100
101
102
103
104
105
106
# File 'lib/ruby-processing/runners/watch.rb', line 97

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

#rewind_to_recorded_stateObject

Try to go back to the recorded Ruby state.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/ruby-processing/runners/watch.rb', line 77

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.



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

def start_watching
  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
        begin
          Processing.load_and_run_sketch
        rescue StandardError, ScriptError
          print "Error in your sketch: ", $!, "\n"
        end
      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.



47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/ruby-processing/runners/watch.rb', line 47

def wipe_out_current_app!
  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