bud 0.1.0.pre1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/bud.rb CHANGED
@@ -11,9 +11,7 @@ require 'bud/monkeypatch'
11
11
  require 'bud/aggs'
12
12
  require 'bud/bud_meta'
13
13
  require 'bud/collections'
14
- require 'bud/joins'
15
14
  require 'bud/metrics'
16
- #require 'bud/meta_algebra'
17
15
  require 'bud/rtrace'
18
16
  require 'bud/server'
19
17
  require 'bud/state'
@@ -62,12 +60,11 @@ $bud_instances = {} # Map from instance id => Bud instance
62
60
  #
63
61
  # :main: Bud
64
62
  module Bud
65
- attr_reader :strata, :budtime, :inbound, :options, :meta_parser, :viz, :rtracer
66
- attr_reader :dsock
67
- attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :sources, :sinks, :app_tables
63
+ attr_reader :budtime, :inbound, :options, :meta_parser, :viz, :rtracer, :dsock
64
+ attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables
68
65
  attr_reader :push_sources, :push_elems, :push_joins, :scanners, :merge_targets, :done_wiring
69
- attr_reader :this_stratum, :this_rule, :rule_orig_src, :done_bootstrap, :done_wiring
70
- attr_accessor :stratum_collection_map, :stratified_rules
66
+ attr_reader :this_stratum, :this_rule, :rule_orig_src, :done_bootstrap
67
+ attr_accessor :stratified_rules
71
68
  attr_accessor :metrics, :periodics
72
69
  attr_accessor :this_rule_context, :qualified_name
73
70
  attr_reader :running_async
@@ -82,7 +79,7 @@ module Bud
82
79
  # * operating system interaction
83
80
  # * <tt>:stdin</tt> if non-nil, reading from the +stdio+ collection results in reading from this +IO+ handle
84
81
  # * <tt>:stdout</tt> writing to the +stdio+ collection results in writing to this +IO+ handle; defaults to <tt>$stdout</tt>
85
- # * <tt>:signal_handling</tt> how to handle +SIGINT+ and +SIGTERM+. If :none, these signals are ignored. If :bloom, they're passed into the built-in scratch called +signals+. Else shutdown all bud instances.
82
+ # * <tt>:signal_handling</tt> how to handle +SIGINT+ and +SIGTERM+. If +:none+, these signals are ignored. Else shutdown all bud instances.
86
83
  # * tracing and output
87
84
  # * <tt>:quiet</tt> if true, suppress certain messages
88
85
  # * <tt>:trace</tt> if true, generate +budvis+ outputs
@@ -106,19 +103,18 @@ module Bud
106
103
  # * <tt>:dbm_truncate</tt> if true, DBM-backed collections are opened with +OTRUNC+
107
104
  def initialize(options={})
108
105
  # capture the binding for a subsequent 'eval'. This ensures that local
109
- # variable names introduced later in this method don't interfere with
106
+ # variable names introduced later in this method don't interfere with
110
107
  # table names used in the eval block.
111
108
  options[:dump_rewrite] ||= ENV["BUD_DUMP_REWRITE"].to_i > 0
112
109
  options[:dump_ast] ||= ENV["BUD_DUMP_AST"].to_i > 0
113
110
  options[:print_wiring] ||= ENV["BUD_PRINT_WIRING"].to_i > 0
114
111
  @qualified_name = ""
115
112
  @tables = {}
116
- @table_meta = []
117
- @stratified_rules = []
118
113
  @channels = {}
119
- @push_elems = {}
120
114
  @dbm_tables = {}
121
115
  @zk_tables = {}
116
+ @stratified_rules = []
117
+ @push_elems = {}
122
118
  @callbacks = {}
123
119
  @callback_id = 0
124
120
  @shutdown_callbacks = {}
@@ -133,15 +129,10 @@ module Bud
133
129
  @done_bootstrap = false
134
130
  @done_wiring = false
135
131
  @instance_id = ILLEGAL_INSTANCE_ID # Assigned when we start running
136
- @sources = {}
137
- @sinks = {}
138
132
  @metrics = {}
139
133
  @endtime = nil
140
134
  @this_stratum = 0
141
135
  @push_sorted_elems = nil
142
-
143
- # XXX This variable is unused in the Push executor
144
- @stratum_first_iter = false
145
136
  @running_async = false
146
137
  @bud_started = false
147
138
 
@@ -155,10 +146,7 @@ module Bud
155
146
  # number won't be known until we start EM
156
147
 
157
148
  builtin_state
158
-
159
149
  resolve_imports
160
-
161
- # Invoke all the user-defined state blocks and initialize builtin state.
162
150
  call_state_methods
163
151
 
164
152
  @declarations = self.class.instance_methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s}
@@ -173,7 +161,7 @@ module Bud
173
161
  @scanners = num_strata.times.map{{}}
174
162
  @push_sources = num_strata.times.map{{}}
175
163
  @push_joins = num_strata.times.map{[]}
176
- @merge_targets = num_strata.times.map{{}}
164
+ @merge_targets = num_strata.times.map{Set.new}
177
165
  end
178
166
  end
179
167
 
@@ -182,7 +170,7 @@ module Bud
182
170
  begin
183
171
  klass = Module.const_get(class_name.to_sym)
184
172
  unless klass.is_a? Class
185
- raise Bud::Error, "Internal error: #{class_name} is in use"
173
+ raise Bud::Error, "internal error: #{class_name} is in use"
186
174
  end
187
175
  rescue NameError # exception if class class_name doesn't exist
188
176
  end
@@ -207,6 +195,11 @@ module Bud
207
195
  instance_variable_get(name) if instance_variable_defined? name
208
196
  end
209
197
 
198
+ def budtime
199
+ toplevel? ? @budtime : toplevel.budtime
200
+ end
201
+
202
+ private
210
203
  def import_defs
211
204
  @imported_defs = {} # mod name -> Module map
212
205
  self.class.ancestors.each do |anc|
@@ -215,7 +208,7 @@ module Bud
215
208
  anc_imp_tbl.each do |nm, mod|
216
209
  # Ensure that two modules have not been imported under one alias.
217
210
  if @imported_defs.member? nm and not @imported_defs[nm] == anc_imp_tbl[nm]
218
- raise Bud::CompileError, "Conflicting imports: modules #{@imported_defs[nm]} and #{anc_imp_tbl[nm]} both are imported as '#{nm}'"
211
+ raise Bud::CompileError, "conflicting imports: modules #{@imported_defs[nm]} and #{anc_imp_tbl[nm]} both are imported as '#{nm}'"
219
212
  end
220
213
  @imported_defs[nm] = mod
221
214
  end
@@ -224,10 +217,6 @@ module Bud
224
217
  @imported_defs ||= self.class.ancestors.inject({}) {|tbl, e| tbl.merge(e.bud_import_table)}
225
218
  end
226
219
 
227
- def budtime
228
- toplevel? ? @budtime : toplevel.budtime
229
- end
230
-
231
220
  # absorb rules and dependencies from imported modules. The corresponding module instantiations
232
221
  # would themselves have resolved their own imports.
233
222
  def resolve_imports
@@ -236,47 +225,51 @@ module Bud
236
225
  import_tbl.each_pair do |local_name, mod_name|
237
226
  # corresponding to "import <mod_name> => :<local_name>"
238
227
  mod_inst = send(local_name)
239
- qlocal_name = toplevel? ? local_name.to_s : self.qualified_name + "." + local_name.to_s
240
228
  if mod_inst.nil?
241
229
  # create wrapper instances
242
230
  #puts "=== resolving #{self}.#{mod_name} => #{local_name}"
243
231
  klass = module_wrapper_class(mod_name)
244
- mod_inst = klass.new(:toplevel => toplevel, :qualified_name => qlocal_name) # this instantiation will resolve the imported module's own imports
232
+ qlocal_name = toplevel? ? local_name.to_s : "#{self.qualified_name}.#{local_name}"
233
+ # this instantiation will resolve the imported module's own imports
234
+ mod_inst = klass.new(:toplevel => toplevel, :qualified_name => qlocal_name)
245
235
  instance_variable_set("@#{local_name}", mod_inst)
246
236
  end
237
+ # Absorb the module wrapper's user-defined state.
247
238
  mod_inst.tables.each_pair do |name, t|
248
- # Absorb the module wrapper's user-defined state.
239
+ # Don't try to import module definitions for builtin Bud state. Note
240
+ # that @tables only includes the builtin tables, because resolve_imports
241
+ # is called before user-defined state blocks are run.
249
242
  unless @tables.has_key? t.tabname
250
- qname = (local_name.to_s + "." + name.to_s).to_sym # access path to table.
251
- tables[qname] = t
243
+ qname = "#{local_name}.#{name}"
244
+ tables[qname.to_sym] = t
252
245
  end
253
246
  end
254
247
  mod_inst.t_rules.each do |imp_rule|
255
- qname = local_name.to_s + "." + imp_rule.lhs.to_s #qualify name by prepending with local_name
248
+ qname = "#{local_name}.#{imp_rule.lhs}"
256
249
  self.t_rules << [imp_rule.bud_obj, imp_rule.rule_id, qname, imp_rule.op,
257
- imp_rule.src, imp_rule.orig_src]
250
+ imp_rule.src, imp_rule.orig_src, imp_rule.nm_funcs_called]
258
251
  end
259
252
  mod_inst.t_depends.each do |imp_dep|
260
- qlname = local_name.to_s + "." + imp_dep.lhs.to_s #qualify names by prepending with local_name
261
- qrname = local_name.to_s + "." + imp_dep.body.to_s
262
- self.t_depends << [imp_dep.bud_obj, imp_dep.rule_id, qlname, imp_dep.op, qrname, imp_dep.nm]
253
+ qlname = "#{local_name}.#{imp_dep.lhs}"
254
+ qrname = "#{local_name}.#{imp_dep.body}"
255
+ self.t_depends << [imp_dep.bud_obj, imp_dep.rule_id, qlname,
256
+ imp_dep.op, qrname, imp_dep.nm]
263
257
  end
264
258
  mod_inst.t_provides.each do |imp_pro|
265
- qintname = local_name.to_s + "." + imp_pro.interface.to_s #qualify names by prepending with local_name
259
+ qintname = "#{local_name}.#{imp_pro.interface}"
266
260
  self.t_provides << [qintname, imp_pro.input]
267
261
  end
268
262
  mod_inst.channels.each do |name, ch|
269
- qname = (local_name.to_s + "." + name.to_s)
263
+ qname = "#{local_name}.#{name}"
270
264
  @channels[qname.to_sym] = ch
271
265
  end
272
266
  mod_inst.dbm_tables.each do |name, t|
273
- qname = (local_name.to_s + "." + name.to_s)
267
+ qname = "#{local_name}.#{name}"
274
268
  @dbm_tables[qname.to_sym] = t
275
269
  end
276
270
  mod_inst.periodics.each do |p|
277
- qname = (local_name.to_s + "." + p.pername.to_s)
278
- p.pername = qname.to_sym
279
- @periodics << [p.pername, p.period]
271
+ qname = "#{local_name}.#{p.pername}"
272
+ @periodics << [qname.to_sym, p.period]
280
273
  end
281
274
  end
282
275
 
@@ -306,47 +299,30 @@ module Bud
306
299
  end
307
300
  end
308
301
 
309
- # Evaluate all bootstrap blocks and tick deltas
310
- def do_bootstrap
311
- # evaluate bootstrap for imported modules
312
- @this_rule_context = self
313
- imported = import_defs.keys
314
- imported.each do |mod_alias|
315
- wrapper = import_instance mod_alias
316
- wrapper.do_bootstrap
317
- end
318
- self.class.ancestors.reverse.each do |anc|
319
- anc.instance_methods(false).each do |m|
320
- if /^__bootstrap__/.match m
321
- self.method(m.to_sym).call
322
- end
323
- end
324
- end
325
- bootstrap
326
-
327
- @tables.each_value {|t| t.bootstrap} if toplevel == self
328
- @done_bootstrap = true
329
- end
330
-
331
302
  def do_wiring
303
+ @num_strata = @stratified_rules.length
304
+
332
305
  @stratified_rules.each_with_index { |rules, stratum| eval_rules(rules, stratum) }
333
306
 
334
- # prepare list of tables that will be actively used at run time. First, all the user defined ones.
335
- # We start @app_tables off as a set, then convert to an array later.
336
- @app_tables = (@tables.keys - @builtin_tables.keys).reduce(Set.new) {|tabset, nm| tabset << @tables[nm]; tabset}
307
+ # Prepare list of tables that will be actively used at run time. First, all
308
+ # the user-defined ones. We start @app_tables off as a set, then convert to
309
+ # an array later.
310
+ @app_tables = (@tables.keys - @builtin_tables.keys).map {|t| @tables[t]}.to_set
311
+
337
312
  # Check scan and merge_targets to see if any builtin_tables need to be added as well.
338
313
  @scanners.each do |scs|
339
- scs.each_value {|s| @app_tables << s.collection}
314
+ @app_tables += scs.values.map {|s| s.collection}
315
+ end
316
+ @merge_targets.each do |mts| #mts == merge_targets at stratum
317
+ @app_tables += mts
340
318
  end
341
- @merge_targets.each{|mts| #mts == merge_targets at stratum
342
- mts.each_key {|t| @app_tables << t}
343
- }
344
319
  @app_tables = @app_tables.nil? ? [] : @app_tables.to_a
345
320
 
346
321
  # for each stratum create a sorted list of push elements in topological order
347
322
  @push_sorted_elems = []
348
323
  @scanners.each do |scs| # scs's values constitute scanners at a stratum
349
- # start with scanners and transitively add all reachable elements in a breadth-first order
324
+ # start with scanners and transitively add all reachable elements in a
325
+ # breadth-first order
350
326
  working = scs.values
351
327
  seen = Set.new(working)
352
328
  sorted_elems = [] # sorted elements in this stratum
@@ -366,9 +342,9 @@ module Bud
366
342
  @push_sorted_elems << sorted_elems
367
343
  end
368
344
 
369
- @merge_targets.each_with_index do |stratum_tables, stratum|
345
+ @merge_targets.each_with_index do |stratum_targets, stratum|
370
346
  @scanners[stratum].each_value do |s|
371
- stratum_tables[s.collection] = true
347
+ stratum_targets << s.collection
372
348
  end
373
349
  end
374
350
 
@@ -379,12 +355,27 @@ module Bud
379
355
  end
380
356
  end
381
357
 
358
+ # create sets of elements and collections to invalidate or rescan at the beginning of each tick.
382
359
  prepare_invalidation_scheme
383
360
 
361
+ # For all tables that are accessed (scanned) in a stratum higher than the one they are updated in, set
362
+ # a flag to track deltas accumulated in that tick (see: collection.tick_delta)
363
+ stratum_accessed = {}
364
+ (@num_strata-1).downto(0) do |stratum|
365
+ @scanners[stratum].each_value do |s|
366
+ stratum_accessed[s.collection] ||= stratum
367
+ end
368
+ end
369
+ @merge_targets.each_with_index do |stratum_targets, stratum|
370
+ stratum_targets.each {|tab|
371
+ tab.accumulate_tick_deltas = true if stratum_accessed[tab] and stratum_accessed[tab] > stratum
372
+ }
373
+ end
374
+
384
375
  @done_wiring = true
385
376
  if @options[:print_wiring]
386
- @push_sources.each do |strat|
387
- strat.each_value{|src| src.print_wiring}
377
+ @push_sources.each do |strat|
378
+ strat.each_value {|src| src.print_wiring}
388
379
  end
389
380
  end
390
381
  end
@@ -394,9 +385,9 @@ module Bud
394
385
  # permits us to preserve data from one tick to the next, and to keep things in incremental mode unless there's a
395
386
  # negation.
396
387
  # This scheme solves the following constraints.
397
- # 1. A full scan of an elements contents results in downstream elements getting full scans themselves (i.e no \
388
+ # 1. A full scan of an element's contents results in downstream elements getting full scans themselves (i.e no \
398
389
  # deltas). This effect is transitive.
399
- # 2. Invalidation of an element's cache results in rebuilding of the cache and a consequent fullscan
390
+ # 2. Invalidation of an element's cache results in rebuilding of the cache and a consequent fullscan. See next.
400
391
  # 3. Invalidation of an element requires upstream elements to rescan their contents, or to transitively pass the
401
392
  # request on further upstream. Any element that has a cache can rescan without passing on the request to higher
402
393
  # levels.
@@ -407,16 +398,13 @@ module Bud
407
398
  # scanner[stratum].invalidate = Set of elements to additionally invalidate if the scanner's table is invalidated at
408
399
  # run-time
409
400
  # scanner[stratum].rescan = Similar to above.
410
-
411
-
412
401
  def prepare_invalidation_scheme
413
- num_strata = @push_sorted_elems.size
402
+ num_strata = @push_sorted_elems.size
414
403
  if $BUD_SAFE
415
404
  @app_tables = @tables.values # No tables excluded
416
405
 
417
- invalidate = Set.new
418
406
  rescan = Set.new
419
- @app_tables.each {|t| invalidate << t if (t.class <= BudScratch)}
407
+ invalidate = @app_tables.select {|t| t.class <= BudScratch}.to_set
420
408
  num_strata.times do |stratum|
421
409
  @push_sorted_elems[stratum].each do |elem|
422
410
  invalidate << elem
@@ -430,35 +418,53 @@ module Bud
430
418
  return
431
419
  end
432
420
 
421
+ # By default, all tables are considered sources unless they appear on the
422
+ # lhs. We only consider non-temporal rules because invalidation is only
423
+ # about this tick. Also, we track (in nm_targets) those tables that are the
424
+ # targets of user-defined code blocks that call non-monotonic functions
425
+ # (such as budtime). Elements that feed these tables are forced to rescan
426
+ # their contents, and thus forced to re-execute these code blocks.
427
+ nm_targets = Set.new
428
+ t_rules.each do |rule|
429
+ lhs = rule.lhs.to_sym
430
+ @tables[lhs].is_source = false if rule.op == "<="
431
+ nm_targets << lhs if rule.nm_funcs_called
432
+ end
433
433
 
434
- # Any table that occurs on the lhs of rule is not considered a source (by default it is).
435
- # In addition, we only consider non-temporal rules because invalidation is only about this tick.
436
- t_rules.each {|rule| @tables[rule.lhs.to_sym].is_source = false if rule.op == "<="}
437
-
438
- invalidate = Set.new
434
+ # Compute a set of tables and elements that should be explicitly told to
435
+ # invalidate or rescan. Start with a set of tables that always invalidate
436
+ # and elements that always rescan.
437
+ invalidate = @app_tables.select {|t| t.invalidate_at_tick}.to_set
439
438
  rescan = Set.new
440
- # Compute a set of tables and elements that should be explicitly told to invalidate or rescan.
441
- # Start with a set of tables that always invalidate, and elements that always rescan
442
- @app_tables.each {|t| invalidate << t if t.invalidate_at_tick}
439
+
443
440
  num_strata.times do |stratum|
444
441
  @push_sorted_elems[stratum].each do |elem|
445
- rescan << elem if elem.rescan_at_tick
442
+ if elem.rescan_at_tick
443
+ rescan << elem
444
+ end
445
+
446
+ if elem.outputs.any?{|tab| not(tab.class <= PushElement) and nm_targets.member? tab.qualified_tabname.to_sym }
447
+ rescan += elem.wired_by
448
+ end
446
449
  end
447
450
  rescan_invalidate_tc(stratum, rescan, invalidate)
448
451
  end
452
+
449
453
  prune_rescan_invalidate(rescan, invalidate)
450
454
  # transitive closure
451
455
  @default_rescan = rescan.to_a
452
456
  @default_invalidate = invalidate.to_a
453
457
 
454
- # Now compute for each table that is to be scanned, the set of dependent tables and elements that will be invalidated
455
- # if that table were to be invalidated at run time.
458
+ # Now compute for each table that is to be scanned, the set of dependent
459
+ # tables and elements that will be invalidated if that table were to be
460
+ # invalidated at run time.
456
461
  dflt_rescan = rescan
457
462
  dflt_invalidate = invalidate
458
463
  to_reset = rescan + invalidate
459
464
  num_strata.times do |stratum|
460
465
  @scanners[stratum].each_value do |scanner|
461
- # If it is going to be always invalidated, it doesn't need further examination.
466
+ # If it is going to be always invalidated, it doesn't need further
467
+ # examination
462
468
  next if dflt_rescan.member? scanner
463
469
 
464
470
  rescan = dflt_rescan + [scanner] # add scanner to scan set
@@ -466,7 +472,8 @@ module Bud
466
472
  rescan_invalidate_tc(stratum, rescan, invalidate)
467
473
  prune_rescan_invalidate(rescan, invalidate)
468
474
  to_reset += rescan + invalidate
469
- # Give the diffs (from default) to scanner; these are elements that are dependent on this scanner
475
+ # Give the diffs (from default) to scanner; these are elements that are
476
+ # dependent on this scanner
470
477
  diffscan = (rescan - dflt_rescan).find_all {|elem| elem.class <= PushElement}
471
478
  scanner.invalidate_at_tick(diffscan, (invalidate - dflt_invalidate).to_a)
472
479
  end
@@ -474,13 +481,13 @@ module Bud
474
481
  @reset_list = to_reset.to_a
475
482
  end
476
483
 
477
-
478
- #given rescan, invalidate sets, compute transitive closure
484
+ # given rescan, invalidate sets, compute transitive closure
479
485
  def rescan_invalidate_tc(stratum, rescan, invalidate)
480
486
  rescan_len = rescan.size
481
487
  invalidate_len = invalidate.size
482
488
  while true
483
- # Ask each element if it wants to add itself to either set, depending on who else is in those sets already.
489
+ # Ask each element if it wants to add itself to either set, depending on
490
+ # who else is in those sets already.
484
491
  @push_sorted_elems[stratum].each {|t| t.add_rescan_invalidate(rescan, invalidate)}
485
492
  break if rescan_len == rescan.size and invalidate_len == invalidate.size
486
493
  rescan_len = rescan.size
@@ -533,10 +540,10 @@ module Bud
533
540
  @rtracer.sleep if options[:rtrace]
534
541
  end
535
542
 
536
- # Startup a Bud instance. This starts EventMachine (if needed) and binds to a
537
- # UDP server socket. If do_tick is true, we also execute a single Bloom
538
- # timestep. Regardless, calling this method does NOT cause Bud to begin
539
- # executing timesteps asynchronously (see run_bg).
543
+ # Startup a Bud instance if it is not currently started. This starts
544
+ # EventMachine (if needed) and binds to a UDP server socket. If do_tick is
545
+ # true, we also execute a single Bloom timestep. Regardless, calling this
546
+ # method does NOT cause Bud to begin running asynchronously (see run_bg).
540
547
  def start(do_tick=false)
541
548
  start_reactor
542
549
  schedule_and_wait do
@@ -703,21 +710,26 @@ module Bud
703
710
  # Note that registering callbacks on persistent collections (e.g., tables,
704
711
  # syncs and stores) is probably not wise: as long as any tuples are stored in
705
712
  # the collection, the callback will be invoked at the end of every tick.
706
- def register_callback(tbl_name, &block)
713
+ def register_callback(tbl_name, &blk)
707
714
  # We allow callbacks to be added before or after EM has been started. To
708
715
  # simplify matters, we start EM if it hasn't been started yet.
709
716
  start_reactor
710
717
  cb_id = nil
711
718
  schedule_and_wait do
712
- unless @tables.has_key? tbl_name
713
- raise Bud::Error, "no such collection: #{tbl_name}"
714
- end
719
+ cb_id = do_register_callback(tbl_name, &blk)
720
+ end
721
+ return cb_id
722
+ end
715
723
 
716
- raise Bud::Error if @callbacks.has_key? @callback_id
717
- @callbacks[@callback_id] = [tbl_name, block]
718
- cb_id = @callback_id
719
- @callback_id += 1
724
+ def do_register_callback(tbl_name, &blk)
725
+ unless @tables.has_key? tbl_name
726
+ raise Bud::Error, "no such collection: #{tbl_name}"
720
727
  end
728
+
729
+ raise Bud::Error if @callbacks.has_key? @callback_id
730
+ @callbacks[@callback_id] = [tbl_name, blk]
731
+ cb_id = @callback_id
732
+ @callback_id += 1
721
733
  return cb_id
722
734
  end
723
735
 
@@ -735,10 +747,6 @@ module Bud
735
747
  # inserted into the output collection: these are returned to the caller.
736
748
  def sync_callback(in_tbl, tupleset, out_tbl)
737
749
  q = Queue.new
738
- # XXXX Why two separate entrances into the event loop? Why is the callback not registered in the sync_do below?
739
- cb = register_callback(out_tbl) do |c|
740
- q.push c.to_a
741
- end
742
750
 
743
751
  # If the runtime shuts down before we see anything in the output collection,
744
752
  # make sure we hear about it so we can raise an error
@@ -747,16 +755,21 @@ module Bud
747
755
  q.push :callback
748
756
  end
749
757
 
750
- unless in_tbl.nil?
751
- sync_do {
758
+ cb = nil
759
+ sync_do {
760
+ if in_tbl
752
761
  t = @tables[in_tbl]
753
762
  if t.class <= Bud::BudChannel or t.class <= Bud::BudZkTable
754
763
  t <~ tupleset
755
764
  else
756
765
  t <+ tupleset
757
766
  end
758
- }
759
- end
767
+ end
768
+
769
+ cb = do_register_callback(out_tbl) do |c|
770
+ q.push c.to_a
771
+ end
772
+ }
760
773
  result = q.pop
761
774
  if result == :callback
762
775
  # Don't try to unregister the callbacks first: runtime is already shutdown
@@ -764,7 +777,7 @@ module Bud
764
777
  end
765
778
  unregister_callback(cb)
766
779
  cancel_shutdown_cb(shutdown_cb)
767
- return (result == :callback) ? nil : result
780
+ return result
768
781
  end
769
782
 
770
783
  # A common special case for sync_callback: block on a delta to a table.
@@ -848,7 +861,7 @@ module Bud
848
861
  # instance that hasn't been started yet.
849
862
  return if @instance_id == ILLEGAL_INSTANCE_ID
850
863
  $signal_lock.synchronize {
851
- raise unless $bud_instances.has_key? @instance_id
864
+ raise Bud::Error unless $bud_instances.has_key? @instance_id
852
865
  $bud_instances.delete @instance_id
853
866
  @instance_id = ILLEGAL_INSTANCE_ID
854
867
  }
@@ -911,6 +924,28 @@ module Bud
911
924
  start(true)
912
925
  end
913
926
 
927
+ # Evaluate all bootstrap blocks and tick deltas
928
+ def do_bootstrap
929
+ # Evaluate bootstrap for imported modules
930
+ @this_rule_context = self
931
+ imported = import_defs.keys
932
+ imported.each do |mod_alias|
933
+ wrapper = import_instance mod_alias
934
+ wrapper.do_bootstrap
935
+ end
936
+ self.class.ancestors.reverse.each do |anc|
937
+ anc.instance_methods(false).each do |m|
938
+ if /^__bootstrap__/.match m
939
+ self.method(m.to_sym).call
940
+ end
941
+ end
942
+ end
943
+ bootstrap
944
+
945
+ @tables.each_value {|t| t.bootstrap} if toplevel == self
946
+ @done_bootstrap = true
947
+ end
948
+
914
949
  # One timestep of Bloom execution. This MUST be invoked from the EventMachine
915
950
  # thread; it is not intended to be called directly by client code.
916
951
  def tick_internal
@@ -919,10 +954,11 @@ module Bud
919
954
  starttime = Time.now if options[:metrics]
920
955
  if options[:metrics] and not @endtime.nil?
921
956
  @metrics[:betweentickstats] ||= initialize_stats
922
- @metrics[:betweentickstats] = running_stats(@metrics[:betweentickstats], starttime - @endtime)
957
+ @metrics[:betweentickstats] = running_stats(@metrics[:betweentickstats],
958
+ starttime - @endtime)
923
959
  end
924
960
  @inside_tick = true
925
-
961
+
926
962
  unless @done_bootstrap
927
963
  do_bootstrap
928
964
  do_wiring
@@ -932,27 +968,28 @@ module Bud
932
968
  @default_rescan.each {|elem| elem.rescan = true}
933
969
  @default_invalidate.each {|elem|
934
970
  elem.invalidated = true
935
- elem.invalidate_cache unless elem.class <= PushElement # call tick on tables here itself. The rest below.
971
+ # Call tick on tables here itself. The rest below
972
+ elem.invalidate_cache unless elem.class <= PushElement
936
973
  }
937
974
 
938
975
  num_strata = @push_sorted_elems.size
939
- # The following loop invalidates additional (non-default) elements and tables that depend on the run-time
940
- # invalidation state of a table.
976
+ # The following loop invalidates additional (non-default) elements and
977
+ # tables that depend on the run-time invalidation state of a table.
941
978
  # Loop once to set the flags
942
979
  num_strata.times do |stratum|
943
980
  @scanners[stratum].each_value do |scanner|
944
981
  if scanner.rescan
945
982
  scanner.rescan_set.each {|e| e.rescan = true}
946
983
  scanner.invalidate_set.each {|e|
947
- e.invalidated = true;
984
+ e.invalidated = true
948
985
  e.invalidate_cache unless e.class <= PushElement
949
- }
986
+ }
950
987
  end
951
988
  end
952
989
  end
953
- #Loop a second time to actually call invalidate_cache
990
+ # Loop a second time to actually call invalidate_cache
954
991
  num_strata.times do |stratum|
955
- @push_sorted_elems[stratum].each { |elem| elem.invalidate_cache if elem.invalidated}
992
+ @push_sorted_elems[stratum].each {|e| e.invalidate_cache if e.invalidated}
956
993
  end
957
994
  end
958
995
 
@@ -970,20 +1007,20 @@ module Bud
970
1007
  # tick deltas on any merge targets and look for more deltas
971
1008
  # check to see if any joins saw a delta
972
1009
  push_joins[stratum].each do |p|
973
- if p.found_delta==true
974
- fixpoint = false
1010
+ if p.found_delta
1011
+ fixpoint = false
975
1012
  p.tick_deltas
976
1013
  end
977
1014
  end
978
- merge_targets[stratum].each_key do |t|
1015
+ merge_targets[stratum].each do |t|
979
1016
  fixpoint = false if t.tick_deltas
980
1017
  end
981
1018
  end
982
1019
  # push end-of-fixpoint
983
- @push_sorted_elems[stratum].each {|p|
1020
+ @push_sorted_elems[stratum].each do |p|
984
1021
  p.stratum_end
985
- }
986
- merge_targets[stratum].each_key do |t|
1022
+ end
1023
+ merge_targets[stratum].each do |t|
987
1024
  t.flush_deltas
988
1025
  end
989
1026
  end
@@ -1019,7 +1056,7 @@ module Bud
1019
1056
 
1020
1057
  private
1021
1058
 
1022
- # Builtin BUD state (predefined collections). We could define this using the
1059
+ # Builtin Bud state (predefined collections). We could define this using the
1023
1060
  # standard state block syntax, but we want to ensure that builtin state is
1024
1061
  # initialized before user-defined state.
1025
1062
  def builtin_state
@@ -1028,12 +1065,11 @@ module Bud
1028
1065
 
1029
1066
  loopback :localtick, [:col1]
1030
1067
  @stdio = terminal :stdio
1031
- signal :signals, [:name]
1032
1068
  scratch :halt, [:key]
1033
1069
  @periodics = table :periodics_tbl, [:pername] => [:period]
1034
1070
 
1035
1071
  # for BUD reflection
1036
- table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src]
1072
+ table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src, :nm_funcs_called]
1037
1073
  table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm]
1038
1074
  table :t_provides, [:interface] => [:input]
1039
1075
  table :t_underspecified, t_provides.schema
@@ -1072,10 +1108,10 @@ module Bud
1072
1108
  end
1073
1109
 
1074
1110
  def eval_rules(rules, strat_num)
1075
- # This routine evals the rules in a given stratum, which results in a wiring of PushElements
1076
- @this_stratum = strat_num
1111
+ # This routine evals the rules in a given stratum, which results in a wiring
1112
+ # of PushElements
1113
+ @this_stratum = strat_num
1077
1114
  rules.each_with_index do |rule, i|
1078
- @this_rule = i
1079
1115
  @this_rule_context = rule.bud_obj # user-supplied code blocks will be evaluated in this context at run-time
1080
1116
  begin
1081
1117
  eval_rule(rule.bud_obj, rule.src)
@@ -1123,7 +1159,7 @@ module Bud
1123
1159
  Bud.shutdown_all_instances(false)
1124
1160
 
1125
1161
  $got_shutdown_signal = false
1126
- $setup_signal_handler = false
1162
+ $signal_handler_setup = false
1127
1163
 
1128
1164
  yield
1129
1165
  end
@@ -1146,15 +1182,11 @@ module Bud
1146
1182
  $signal_lock.synchronize {
1147
1183
  # If we setup signal handlers and then fork a new process, we want to
1148
1184
  # reinitialize the signal handler in the child process.
1149
- unless b.options[:signal_handling] == :none or $signal_handler_setup
1185
+ unless b.options[:signal_handling] == :none || $signal_handler_setup
1150
1186
  EventMachine::PeriodicTimer.new(SIGNAL_CHECK_PERIOD) do
1151
1187
  if $got_shutdown_signal
1152
- if b.options[:signal_handling] == :bloom
1153
- Bud.tick_all_instances
1154
- else
1155
- Bud.shutdown_all_instances
1156
- Bud.stop_em_loop
1157
- end
1188
+ Bud.shutdown_all_instances
1189
+ Bud.stop_em_loop
1158
1190
  $got_shutdown_signal = false
1159
1191
  end
1160
1192
  end
@@ -1162,10 +1194,9 @@ module Bud
1162
1194
  ["INT", "TERM"].each do |signal|
1163
1195
  Signal.trap(signal) {
1164
1196
  $got_shutdown_signal = true
1165
- b.sync_do{b.signals.pending_merge([[signal]])}
1166
1197
  }
1167
1198
  end
1168
- $setup_signal_handler_pid = true
1199
+ $signal_handler_setup = true
1169
1200
  end
1170
1201
 
1171
1202
  $instance_id += 1
@@ -1174,15 +1205,6 @@ module Bud
1174
1205
  }
1175
1206
  end
1176
1207
 
1177
- def self.tick_all_instances
1178
- instances = nil
1179
- $signal_lock.synchronize {
1180
- instances = $bud_instances.clone
1181
- }
1182
-
1183
- instances.each_value {|b| b.sync_do }
1184
- end
1185
-
1186
1208
  def self.shutdown_all_instances(do_shutdown_cb=true)
1187
1209
  instances = nil
1188
1210
  $signal_lock.synchronize {