durable_rules 0.31.1

Sign up to get free protection for your applications and to get access to all the features.
data/librb/engine.rb ADDED
@@ -0,0 +1,1015 @@
1
+ require "json"
2
+ require "timers"
3
+ require_relative "../src/rulesrb/rules"
4
+
5
+ module Engine
6
+
7
+ class Closure
8
+ attr_reader :handle, :ruleset_name, :_timers, :_branches, :_messages, :_facts, :_retract
9
+ attr_accessor :s
10
+
11
+ def initialize(state, message, handle, ruleset_name)
12
+ @s = Content.new(state)
13
+ @ruleset_name = ruleset_name
14
+ @handle = handle
15
+ @_timers = {}
16
+ @_messages = {}
17
+ @_branches = {}
18
+ @_facts = {}
19
+ @_retract = {}
20
+ if message.kind_of? Hash
21
+ @m = message
22
+ else
23
+ @m = []
24
+ for one_message in message do
25
+ if (one_message.key? "m") && (one_message.size == 1)
26
+ one_message = one_message["m"]
27
+ end
28
+ @m << Content.new(one_message)
29
+ end
30
+ end
31
+ end
32
+
33
+ def post(ruleset_name, message = nil)
34
+ if !message
35
+ message = ruleset_name
36
+ ruleset_name = @ruleset_name
37
+ end
38
+
39
+ if message.kind_of? Content
40
+ message = message._d
41
+ end
42
+
43
+ if !(message.key? :sid) && !(message.key? "sid")
44
+ message[:sid] = @s.sid
45
+ end
46
+
47
+ message_list = []
48
+ if @_messages.key? ruleset_name
49
+ message_list = @_messages[ruleset_name]
50
+ else
51
+ @_messages[ruleset_name] = message_list
52
+ end
53
+ message_list << message
54
+ end
55
+
56
+ def start_timer(timer_name, duration)
57
+ if @_timers.key? timer_name
58
+ raise ArgumentError, "Timer with name #{timer_name} already added"
59
+ else
60
+ @_timers[timer_name] = duration
61
+ end
62
+ end
63
+
64
+ def assert(ruleset_name, fact = nil)
65
+ if !fact
66
+ fact = ruleset_name
67
+ ruleset_name = @ruleset_name
68
+ end
69
+
70
+ if fact.kind_of? Content
71
+ fact = fact._d.dup
72
+ end
73
+
74
+ if !(fact.key? :sid) && !(fact.key? "sid")
75
+ fact[:sid] = @s.sid
76
+ end
77
+
78
+ fact_list = []
79
+ if @_facts.key? ruleset_name
80
+ fact_list = @_facts[ruleset_name]
81
+ else
82
+ @_facts[ruleset_name] = fact_list
83
+ end
84
+ fact_list << fact
85
+ end
86
+
87
+ def retract(ruleset_name, fact = nil)
88
+ if !fact
89
+ fact = ruleset_name
90
+ ruleset_name = @ruleset_name
91
+ end
92
+
93
+ if fact.kind_of? Content
94
+ fact = fact._d.dup
95
+ end
96
+
97
+ if !(fact.key? :sid) && !(fact.key? "sid")
98
+ fact[:sid] = @s.sid
99
+ end
100
+
101
+ fact_list = []
102
+ if @_retract.key? ruleset_name
103
+ fact_list = @_retract[ruleset_name]
104
+ else
105
+ @_retract[ruleset_name] = fact_list
106
+ end
107
+ fact_list << fact
108
+ end
109
+
110
+ private
111
+
112
+ def handle_property(name, value=nil)
113
+ name = name.to_s
114
+ if name.end_with? '?'
115
+ @m.key? name[0..-2]
116
+ elsif @m.kind_of? Hash
117
+ current = @m[name]
118
+ if current.kind_of? Hash
119
+ Content.new current
120
+ else
121
+ current
122
+ end
123
+ else
124
+ @m
125
+ end
126
+ end
127
+
128
+ alias method_missing handle_property
129
+
130
+ end
131
+
132
+
133
+ class Content
134
+ attr_reader :_d
135
+
136
+ def initialize(data)
137
+ @_d = data
138
+ end
139
+
140
+ def to_s
141
+ @_d.to_s
142
+ end
143
+
144
+ private
145
+
146
+ def handle_property(name, value=nil)
147
+ name = name.to_s
148
+ if name.end_with? '='
149
+ @_d[name[0..-2]] = value
150
+ nil
151
+ elsif name.end_with? '?'
152
+ @_d.key? name[0..-2]
153
+ else
154
+ current = @_d[name]
155
+ if current.kind_of? Hash
156
+ Content.new current
157
+ else
158
+ current
159
+ end
160
+ end
161
+ end
162
+
163
+ alias method_missing handle_property
164
+
165
+ end
166
+
167
+
168
+ class Promise
169
+ attr_accessor :root
170
+
171
+ def initialize(func)
172
+ @func = func
173
+ @next = nil
174
+ @sync = true
175
+ @root = self
176
+ end
177
+
178
+ def continue_with(next_func)
179
+ if next_func.kind_of? Promise
180
+ @next = next_func
181
+ elsif next_func.kind_of? Proc
182
+ @next = Promise.new next_func
183
+ else
184
+ raise ArgumentError, "Unexpected Promise Type #{next_func}"
185
+ end
186
+
187
+ @next.root = @root
188
+ @next
189
+ end
190
+
191
+ def run(c, complete)
192
+ if @sync
193
+ begin
194
+ @func.call c
195
+ rescue Exception => e
196
+ complete.call e
197
+ return
198
+ end
199
+
200
+ if @next
201
+ @next.run c, complete
202
+ else
203
+ complete.call nil
204
+ end
205
+ else
206
+ begin
207
+ @func.call c, -> e {
208
+ if e
209
+ complete.call e
210
+ elsif @next
211
+ @next.run c, complete
212
+ else
213
+ complete.call nil
214
+ end
215
+ }
216
+ rescue Exception => e
217
+ complete.call e
218
+ end
219
+ end
220
+ end
221
+
222
+ end
223
+
224
+
225
+ class To < Promise
226
+
227
+ def initialize(from_state, to_state, assert_state)
228
+ super -> c {
229
+ c.s.running = true
230
+ if from_state != to_state
231
+ if from_state
232
+ if c.m && (c.m.kind_of? Array)
233
+ c.retract c.m[0].chart_context
234
+ else
235
+ c.retract c.chart_context
236
+ end
237
+ end
238
+
239
+ id = rand(1000000000)
240
+ if assert_state
241
+ c.assert(:label => to_state, :chart => 1, :id => id)
242
+ else
243
+ c.post(:label => to_state, :chart => 1, :id => id)
244
+ end
245
+ end
246
+ }
247
+ end
248
+
249
+ end
250
+
251
+
252
+ class Ruleset
253
+ attr_reader :definition
254
+
255
+ def initialize(name, host, ruleset_definition, state_cache_size)
256
+ @actions = {}
257
+ @name = name
258
+ @host = host
259
+ for rule_name, rule in ruleset_definition do
260
+ rule_name = rule_name.to_s
261
+ action = nil
262
+ if rule.key? "run"
263
+ action = rule["run"]
264
+ rule.delete "run"
265
+ elsif rule.key? :run
266
+ action = rule[:run]
267
+ rule.delete :run
268
+ end
269
+
270
+ if !action
271
+ raise ArgumentError, "Action for #{rule_name} is null"
272
+ elsif action.kind_of? String
273
+ @actions[rule_name] = Promise.new host.get_action action
274
+ elsif action.kind_of? Promise
275
+ @actions[rule_name] = action.root
276
+ elsif action.kind_of? Proc
277
+ @actions[rule_name] = Promise.new action
278
+ end
279
+ end
280
+
281
+ @handle = Rules.create_ruleset name, JSON.generate(ruleset_definition), state_cache_size
282
+ @definition = ruleset_definition
283
+ end
284
+
285
+ def bind(databases)
286
+ for db in databases do
287
+ if db.kind_of? String
288
+ Rules.bind_ruleset @handle, db, 0, nil
289
+ else
290
+ Rules.bind_ruleset @handle, db[:host], db[:port], db[:password]
291
+ end
292
+ end
293
+ end
294
+
295
+ def assert_event(message)
296
+ Rules.assert_event @handle, JSON.generate(message)
297
+ end
298
+
299
+ def start_assert_event(message)
300
+ return Rules.start_assert_event @handle, JSON.generate(message)
301
+ end
302
+
303
+ def assert_events(messages)
304
+ Rules.assert_events @handle, JSON.generate(messages)
305
+ end
306
+
307
+ def start_assert_events(messages)
308
+ return Rules.start_assert_events @handle, JSON.generate(messages)
309
+ end
310
+
311
+ def start_timer(sid, timer_name, timer_duration)
312
+ timer = {:sid => sid, :id => rand(1000000000), :$t => timer_name}
313
+ Rules.start_timer @handle, sid.to_s, timer_duration, JSON.generate(timer)
314
+ end
315
+
316
+ def assert_fact(fact)
317
+ Rules.assert_fact @handle, JSON.generate(fact)
318
+ end
319
+
320
+ def start_assert_fact(fact)
321
+ return Rules.start_assert_fact @handle, JSON.generate(fact)
322
+ end
323
+
324
+ def assert_facts(facts)
325
+ Rules.assert_facts @handle, JSON.generate(facts)
326
+ end
327
+
328
+ def start_assert_facts(facts)
329
+ return Rules.start_assert_facts @handle, JSON.generate(facts)
330
+ end
331
+
332
+ def retract_fact(fact)
333
+ Rules.retract_fact @handle, JSON.generate(fact)
334
+ end
335
+
336
+ def start_retract_fact(fact)
337
+ return Rules.start_retract_fact @handle, JSON.generate(fact)
338
+ end
339
+
340
+ def retract_facts(facts)
341
+ Rules.assert_facts @handle, JSON.generate(facts)
342
+ end
343
+
344
+ def start_retract_facts(facts)
345
+ return Rules.start_retract_facts @handle, JSON.generate(facts)
346
+ end
347
+
348
+ def assert_state(state)
349
+ Rules.assert_state @handle, JSON.generate(state)
350
+ end
351
+
352
+ def get_state(sid)
353
+ JSON.parse Rules.get_state(@handle, sid)
354
+ end
355
+
356
+ def Ruleset.create_rulesets(parent_name, host, ruleset_definitions, state_cache_size)
357
+ branches = {}
358
+ for name, definition in ruleset_definitions do
359
+ name = name.to_s
360
+ if name.end_with? "$state"
361
+ name = name[0..-7]
362
+ name = "#{parent_name}.#{name}" if parent_name
363
+ branches[name] = Statechart.new name, host, definition, state_cache_size
364
+ elsif name.end_with? "$flow"
365
+ name = name[0..-6]
366
+ name = "#{parent_name}.#{name}" if parent_name
367
+ branches[name] = Flowchart.new name, host, definition, state_cache_size
368
+ else
369
+ name = "#{parent_name}.#{name}" if parent_name
370
+ branches[name] = Ruleset.new name, host, definition, state_cache_size
371
+ end
372
+ end
373
+
374
+ branches
375
+ end
376
+
377
+ def dispatch_timers(complete)
378
+ begin
379
+ Rules.assert_timers @handle
380
+ rescue Exception => e
381
+ complete.call e
382
+ return
383
+ end
384
+
385
+ complete.call nil
386
+ end
387
+
388
+ def dispatch(complete)
389
+ result_container = {}
390
+ action_handle = nil
391
+ action_binding = nil
392
+ state = nil
393
+ begin
394
+ result = Rules.start_action @handle
395
+ if result
396
+ state = JSON.parse result[0]
397
+ result_container = {:message => JSON.parse(result[1])}
398
+ action_handle = result[2]
399
+ action_binding = result[3]
400
+ end
401
+ rescue Exception => e
402
+ complete.call e
403
+ return
404
+ end
405
+
406
+ while result_container.key? :message do
407
+ action_name = nil
408
+ for action_name, message in result_container[:message] do
409
+ break
410
+ end
411
+
412
+ result_container.delete :message
413
+ c = Closure.new state, message, action_handle, @name
414
+ @actions[action_name].run c, -> e {
415
+ if e
416
+ Rules.abandon_action @handle, c.handle
417
+ complete.call e
418
+ else
419
+ begin
420
+ for timer_name, timer_duration in c._timers do
421
+ start_timer c.s.sid, timer_name, timer_duration
422
+ end
423
+ binding = 0
424
+ replies = 0
425
+ pending = {action_binding => 0}
426
+ for ruleset_name, facts in c._retract do
427
+ if facts.length == 1
428
+ binding, replies = @host.start_retract ruleset_name, facts[0]
429
+ else
430
+ binding, replies = @host.start_retract_facts ruleset_name, facts
431
+ end
432
+ if pending.key? binding
433
+ pending[binding] = pending[binding] + replies
434
+ else
435
+ pending[binding] = replies
436
+ end
437
+ end
438
+ for ruleset_name, facts in c._facts do
439
+ if facts.length == 1
440
+ binding, replies = @host.start_assert ruleset_name, facts[0]
441
+ else
442
+ binding, replies = @host.start_assert_facts ruleset_name, facts
443
+ end
444
+ if pending.key? binding
445
+ pending[binding] = pending[binding] + replies
446
+ else
447
+ pending[binding] = replies
448
+ end
449
+ end
450
+ for ruleset_name, messages in c._messages do
451
+ if messages.length == 1
452
+ binding, replies = @host.start_post ruleset_name, messages[0]
453
+ else
454
+ binding, replies = @host.start_post_batch ruleset_name, messages
455
+ end
456
+ if pending.key? binding
457
+ pending[binding] = pending[binding] + replies
458
+ else
459
+ pending[binding] = replies
460
+ end
461
+ end
462
+ binding, replies = Rules.start_update_state @handle, c.handle, JSON.generate(c.s._d)
463
+ if pending.key? binding
464
+ pending[binding] = pending[binding] + replies
465
+ else
466
+ pending[binding] = replies
467
+ end
468
+ for binding, replies in pending do
469
+ if binding != 0
470
+ if binding != action_binding
471
+ Rules.complete(binding, replies)
472
+ else
473
+ new_result = Rules.complete_and_start_action @handle, replies, c.handle
474
+ if new_result
475
+ result_container[:message] = JSON.parse new_result
476
+ end
477
+ end
478
+ end
479
+ end
480
+ rescue Exception => e
481
+ Rules.abandon_action @handle, c.handle
482
+ complete.call e
483
+ end
484
+ end
485
+ }
486
+ end
487
+ complete.call nil
488
+ end
489
+
490
+ def to_json
491
+ JSON.generate @definition
492
+ end
493
+
494
+ end
495
+
496
+ class Statechart < Ruleset
497
+
498
+ def initialize(name, host, chart_definition, state_cache_size)
499
+ @name = name
500
+ @host = host
501
+ ruleset_definition = {}
502
+ transform nil, nil, nil, chart_definition, ruleset_definition
503
+ super name, host, ruleset_definition, state_cache_size
504
+ @definition = chart_definition
505
+ @definition[:$type] = "stateChart"
506
+ end
507
+
508
+ def transform(parent_name, parent_triggers, parent_start_state, chart_definition, rules)
509
+ start_state = {}
510
+ reflexive_states = {}
511
+
512
+ for state_name, state in chart_definition do
513
+ qualified_name = state_name.to_s
514
+ qualified_name = "#{parent_name}.#{state_name}" if parent_name
515
+ start_state[qualified_name] = true
516
+
517
+ for trigger_name, trigger in state do
518
+ if ((trigger.key? :to) && (trigger[:to] == state_name)) ||
519
+ ((trigger.key? "to") && (trigger["to"] == state_name)) ||
520
+ (trigger.key? :count) || (trigger.key? "count") ||
521
+ (trigger.key? :cap) || (trigger.key? "cap") ||
522
+ (trigger.key? :span) || (trigger.key? "span")
523
+ reflexive_states[qualified_name] = true
524
+ end
525
+ end
526
+ end
527
+
528
+ for state_name, state in chart_definition do
529
+ qualified_name = state_name.to_s
530
+ qualified_name = "#{parent_name}.#{state_name}" if parent_name
531
+
532
+ triggers = {}
533
+ if parent_triggers
534
+ for parent_trigger_name, trigger in parent_triggers do
535
+ parent_trigger_name = parent_trigger_name.to_s
536
+ trigger_name = parent_trigger_name[parent_trigger_name.rindex('.')..-1]
537
+ triggers["#{qualified_name}.#{trigger_name}"] = trigger
538
+ end
539
+ end
540
+
541
+ for trigger_name, trigger in state do
542
+ trigger_name = trigger_name.to_s
543
+ if trigger_name != "$chart"
544
+ if parent_name && (trigger.key? "to")
545
+ to_name = trigger["to"].to_s
546
+ trigger["to"] = "#{parent_name}.#{to_name}"
547
+ elsif parent_name && (trigger.key? :to)
548
+ to_name = trigger[:to].to_s
549
+ trigger[:to] = "#{parent_name}.#{to_name}"
550
+ end
551
+ triggers["#{qualified_name}.#{trigger_name}"] = trigger
552
+ end
553
+ end
554
+
555
+ if state.key? "$chart"
556
+ transform qualified_name, triggers, start_state, state["$chart"], rules
557
+ elsif state.key? :$chart
558
+ transform qualified_name, triggers, start_state, state[:$chart], rules
559
+ else
560
+ for trigger_name, trigger in triggers do
561
+
562
+ trigger_name = trigger_name.to_s
563
+ rule = {}
564
+ state_test = {:chart_context => {:$and => [{:label => qualified_name}, {:chart => 1}]}}
565
+ if trigger.key? :pri
566
+ rule[:pri] = trigger[:pri]
567
+ elsif trigger.key? "pri"
568
+ rule[:pri] = trigger["pri"]
569
+ end
570
+
571
+ if trigger.key? :count
572
+ rule[:count] = trigger[:count]
573
+ elsif trigger.key? "count"
574
+ rule[:count] = trigger["count"]
575
+ end
576
+
577
+ if trigger.key? :span
578
+ rule[:span] = trigger[:span]
579
+ elsif trigger.key? "span"
580
+ rule[:span] = trigger["span"]
581
+ end
582
+
583
+ if trigger.key? :cap
584
+ rule[:cap] = trigger[:cap]
585
+ elsif trigger.key? "cap"
586
+ rule[:cap] = trigger["cap"]
587
+ end
588
+
589
+ if (trigger.key? :all) || (trigger.key? "all")
590
+ all_trigger = nil
591
+ if trigger.key? :all
592
+ all_trigger = trigger[:all]
593
+ else
594
+ all_trigger = trigger["all"]
595
+ end
596
+ rule[:all] = all_trigger.dup
597
+ rule[:all] << state_test
598
+ elsif (trigger.key? :any) || (trigger.key? "any")
599
+ any_trigger = nil
600
+ if trigger.key? :any
601
+ any_trigger = trigger[:any]
602
+ else
603
+ any_trigger = trigger["any"]
604
+ end
605
+ rule[:all] = [state_test, {"m$any" => any_trigger}]
606
+ else
607
+ rule[:all] = [state_test]
608
+ end
609
+
610
+ if (trigger.key? "run") || (trigger.key? :run)
611
+ trigger_run = nil
612
+ if trigger.key? :run
613
+ trigger_run = trigger[:run]
614
+ else
615
+ trigger_run = trigger["run"]
616
+ end
617
+
618
+ if trigger_run.kind_of? String
619
+ rule[:run] = Promise.new @host.get_action(trigger_run)
620
+ elsif trigger_run.kind_of? Promise
621
+ rule[:run] = trigger_run
622
+ elsif trigger_run.kind_of? Proc
623
+ rule[:run] = Promise.new trigger_run
624
+ end
625
+ end
626
+
627
+ if (trigger.key? "to") || (trigger.key? :to)
628
+ trigger_to = nil
629
+ if trigger.key? :to
630
+ trigger_to = trigger[:to]
631
+ else
632
+ trigger_to = trigger["to"]
633
+ end
634
+ trigger_to = trigger_to.to_s
635
+ from_state = nil
636
+ if reflexive_states.key? qualified_name
637
+ from_state = qualified_name
638
+ end
639
+ assert_state = false
640
+ if reflexive_states.key? trigger_to
641
+ assert_state = true
642
+ end
643
+
644
+ if rule.key? :run
645
+ rule[:run].continue_with To.new(from_state, trigger_to, assert_state)
646
+ else
647
+ rule[:run] = To.new from_state, trigger_to, assert_state
648
+ end
649
+
650
+ start_state.delete trigger_to if start_state.key? trigger_to
651
+ if parent_start_state && (parent_start_state.key? trigger_to)
652
+ parent_start_state.delete trigger_to
653
+ end
654
+ else
655
+ raise ArgumentError, "Trigger #{trigger_name} destination not defined"
656
+ end
657
+
658
+ rules[trigger_name] = rule
659
+ end
660
+ end
661
+ end
662
+
663
+ started = false
664
+ for state_name in start_state.keys do
665
+ raise ArgumentError, "Chart #{@name} has more than one start state" if started
666
+ state_name = state_name.to_s
667
+ started = true
668
+
669
+ if parent_name
670
+ rules[parent_name + "$start"] = {:all => [{:chart_context => {:$and => [{:label => parent_name}, {:chart => 1}]}}], run: To.new(nil, state_name, false)};
671
+ else
672
+ rules[:$start] = {:all => [{:chart_context => {:$and => [{:$nex => {:running => 1}}, {:$s => 1}]}}], run: To.new(nil, state_name, false)};
673
+ end
674
+ end
675
+
676
+ raise ArgumentError, "Chart #{@name} has no start state" if not started
677
+ end
678
+ end
679
+
680
+ class Flowchart < Ruleset
681
+
682
+ def initialize(name, host, chart_definition, state_cache_size)
683
+ @name = name
684
+ @host = host
685
+ ruleset_definition = {}
686
+ transform chart_definition, ruleset_definition
687
+ super name, host, ruleset_definition, state_cache_size
688
+ @definition = chart_definition
689
+ @definition["$type"] = "flowChart"
690
+ end
691
+
692
+ def transform(chart_definition, rules)
693
+ visited = {}
694
+ reflexive_stages = {}
695
+ for stage_name, stage in chart_definition do
696
+ if (stage.key? :to) || (stage.key? "to")
697
+ stage_to = (stage.key? :to) ? stage[:to]: stage["to"]
698
+ if (stage_to.kind_of? String) || (stage_to.kind_of? Symbol)
699
+ if stage_to == stage_name
700
+ reflexive_stages[stage_name] = true
701
+ end
702
+ else
703
+ for transition_name, transition in stage_to do
704
+ if (transition_name == stage_name) ||
705
+ (transition.key? :count) || (transition.key? "count") ||
706
+ (transition.key? :cap) || (transition.key? "cap") ||
707
+ (transition.key? :span) || (transition.key? "span")
708
+ reflexive_stages[stage_name] = true
709
+ end
710
+ end
711
+ end
712
+ end
713
+ end
714
+
715
+ for stage_name, stage in chart_definition do
716
+ from_stage = nil
717
+ if reflexive_stages.key? stage_name
718
+ from_stage = stage_name
719
+ end
720
+
721
+ stage_name = stage_name.to_s
722
+ stage_test = {:chart_context => {:$and => [{:label => stage_name}, {:chart => 1}]}}
723
+ if (stage.key? :to) || (stage.key? "to")
724
+ stage_to = (stage.key? :to) ? stage[:to]: stage["to"]
725
+ if (stage_to.kind_of? String) || (stage_to.kind_of? Symbol)
726
+ next_stage = nil
727
+ rule = {:all => [stage_test]}
728
+ if chart_definition.key? stage_to
729
+ next_stage = chart_definition[stage_to]
730
+ elsif chart_definition.key? stage_to.to_s
731
+ next_stage = chart_definition[stage_to.to_s]
732
+ else
733
+ raise ArgumentError, "Stage #{stage_to.to_s} not found"
734
+ end
735
+
736
+ assert_stage = false
737
+ if reflexive_stages.key? stage_to
738
+ assert_stage = true
739
+ end
740
+
741
+ stage_to = stage_to.to_s
742
+ if !(next_stage.key? :run) && !(next_stage.key? "run")
743
+ rule[:run] = To.new from_stage, stage_to, assert_stage
744
+ else
745
+ next_stage_run = (next_stage.key? :run) ? next_stage[:run]: next_stage["run"]
746
+ if next_stage_run.kind_of? String
747
+ rule[:run] = To.new(from_stage, stage_to, assert_stage).continue_with Promise(@host.get_action(next_stage_run))
748
+ elsif (next_stage_run.kind_of? Promise) || (next_stage_run.kind_of? Proc)
749
+ rule[:run] = To.new(from_stage, stage_to, assert_stage).continue_with next_stage_run
750
+ end
751
+ end
752
+
753
+ rules["#{stage_name}.#{stage_to}"] = rule
754
+ visited[stage_to] = true
755
+ else
756
+ for transition_name, transition in stage_to do
757
+ rule = {}
758
+ next_stage = nil
759
+
760
+ if transition.key? :pri
761
+ rule[:pri] = transition[:pri]
762
+ elsif transition.key? "pri"
763
+ rule[:pri] = transition["pri"]
764
+ end
765
+
766
+ if transition.key? :count
767
+ rule[:count] = transition[:count]
768
+ elsif transition.key? "count"
769
+ rule[:count] = transition["count"]
770
+ end
771
+
772
+ if transition.key? :span
773
+ rule[:span] = transition[:span]
774
+ elsif transition.key? "span"
775
+ rule[:span] = transition["span"]
776
+ end
777
+
778
+ if transition.key? :cap
779
+ rule[:cap] = transition[:cap]
780
+ elsif transition.key? "cap"
781
+ rule[:cap] = transition["cap"]
782
+ end
783
+
784
+ if (transition.key? :all) || (transition.key? "all")
785
+ all_transition = nil
786
+ if transition.key? :all
787
+ all_transition = transition[:all]
788
+ else
789
+ all_transition = transition["all"]
790
+ end
791
+ rule[:all] = all_transition.dup
792
+ rule[:all] << stage_test
793
+ elsif (transition.key? :any) || (transition.key? "any")
794
+ any_transition = nil
795
+ if transition.key? :any
796
+ any_transition = transition[:any]
797
+ else
798
+ any_transition = transition["any"]
799
+ end
800
+ rule[:all] = [stage_test, {"m$any" => any_transition}]
801
+ else
802
+ rule[:all] = [stage_test]
803
+ end
804
+
805
+ if chart_definition.key? transition_name
806
+ next_stage = chart_definition[transition_name]
807
+ else
808
+ raise ArgumentError, "Stage #{transition_name.to_s} not found"
809
+ end
810
+
811
+ assert_stage = false
812
+ if reflexive_stages.key? transition_name
813
+ assert_stage = true
814
+ end
815
+
816
+ transition_name = transition_name.to_s
817
+ if !(next_stage.key? :run) && !(next_stage.key? "run")
818
+ rule[:run] = To.new from_stage, transition_name, assert_stage
819
+ else
820
+ next_stage_run = (next_stage.key? :run) ? next_stage[:run]: next_stage["run"]
821
+ if next_stage_run.kind_of? String
822
+ rule[:run] = To.new(from_stage, transition_name, assert_stage).continue_with Promise(@host.get_action(next_stage_run))
823
+ elsif (next_stage_run.kind_of? Promise) || (next_stage_run.kind_of? Proc)
824
+ rule[:run] = To.new(from_stage, transition_name, assert_stage).continue_with next_stage_run
825
+ end
826
+ end
827
+
828
+ rules["#{stage_name}.#{transition_name}"] = rule
829
+ visited[transition_name] = true
830
+ end
831
+ end
832
+ end
833
+ end
834
+
835
+ started = false
836
+ for stage_name, stage in chart_definition do
837
+ stage_name = stage_name.to_s
838
+ if !(visited.key? stage_name)
839
+ if started
840
+ raise ArgumentError, "Chart #{@name} has more than one start state"
841
+ end
842
+
843
+ started = true
844
+ rule = {:all => [{:chart_context => {:$and => [{:$nex => {:running => 1}}, {:$s => 1}]}}]}
845
+ if !(stage.key? :run) && !(stage.key? "run")
846
+ rule[:run] = To.new nil, stage_name, false
847
+ else
848
+ stage_run = stage.key? :run ? stage[:run]: stage["run"]
849
+ if stage_run.kind_of? String
850
+ rule[:run] = To.new(nil, stage_name, false).continue_with Promise(@host.get_action(stage_run))
851
+ elsif (stage_run.kind_of? Promise) || (stage_run.kind_of? Proc)
852
+ rule[:run] = To.new(nil, stage_name, false).continue_with stage_run
853
+ end
854
+ end
855
+ rules["$start.#{stage_name}"] = rule
856
+ end
857
+ end
858
+ end
859
+ end
860
+
861
+ class Host
862
+
863
+ def initialize(ruleset_definitions = nil, databases = [{:host => 'localhost', :port => 6379, :password => nil}], state_cache_size = 1024)
864
+ @ruleset_directory = {}
865
+ @ruleset_list = []
866
+ @databases = databases
867
+ @state_cache_size = state_cache_size
868
+ register_rulesets nil, ruleset_definitions if ruleset_definitions
869
+ end
870
+
871
+ def get_action(action_name)
872
+ raise ArgumentError, "Action with name #{action_name} not found"
873
+ end
874
+
875
+ def load_ruleset(ruleset_name)
876
+ raise ArgumentError, "Ruleset with name #{ruleset_name} not found"
877
+ end
878
+
879
+ def save_ruleset(ruleset_name, ruleset_definition)
880
+ end
881
+
882
+ def get_ruleset(ruleset_name)
883
+ ruleset_name = ruleset_name.to_s
884
+ if !(@ruleset_directory.key? ruleset_name)
885
+ ruleset_definition = load_ruleset ruleset_name
886
+ register_rulesets nil, ruleset_definition
887
+ end
888
+
889
+ @ruleset_directory[ruleset_name]
890
+ end
891
+
892
+ def set_ruleset(ruleset_name, ruleset_definition)
893
+ register_rulesets nil, ruleset_definition
894
+ save_ruleset ruleset_name, ruleset_definition
895
+ end
896
+
897
+ def get_state(ruleset_name, sid)
898
+ get_ruleset(ruleset_name).get_state sid
899
+ end
900
+
901
+ def post_batch(ruleset_name, *events)
902
+ get_ruleset(ruleset_name).assert_events events
903
+ end
904
+
905
+ def start_post_batch(ruleset_name, *events)
906
+ return get_ruleset(ruleset_name).start_assert_events events
907
+ end
908
+
909
+ def post(ruleset_name, event)
910
+ get_ruleset(ruleset_name).assert_event event
911
+ end
912
+
913
+ def start_post(ruleset_name, event)
914
+ return get_ruleset(ruleset_name).start_assert_event event
915
+ end
916
+
917
+ def assert(ruleset_name, fact)
918
+ get_ruleset(ruleset_name).assert_fact fact
919
+ end
920
+
921
+ def start_assert(ruleset_name, fact)
922
+ return get_ruleset(ruleset_name).start_assert_fact fact
923
+ end
924
+
925
+ def assert_facts(ruleset_name, *facts)
926
+ get_ruleset(ruleset_name).assert_facts facts
927
+ end
928
+
929
+ def start_assert_facts(ruleset_name, *facts)
930
+ return get_ruleset(ruleset_name).start_assert_facts facts
931
+ end
932
+
933
+ def retract(ruleset_name, fact)
934
+ get_ruleset(ruleset_name).retract_fact fact
935
+ end
936
+
937
+ def start_retract(ruleset_name, fact)
938
+ return get_ruleset(ruleset_name).start_retract_fact fact
939
+ end
940
+
941
+ def retract_facts(ruleset_name, *facts)
942
+ get_ruleset(ruleset_name).retract_facts facts
943
+ end
944
+
945
+ def start_retract_facts(ruleset_name, *facts)
946
+ return get_ruleset(ruleset_name).start_retract_facts facts
947
+ end
948
+
949
+ def start_timer(ruleset_name, sid, timer_name, timer_duration)
950
+ get_ruleset(ruleset_name).start_timer sid, timer_name, timer_duration
951
+ end
952
+
953
+ def patch_state(ruleset_name, state)
954
+ get_ruleset(ruleset_name).assert_state state
955
+ end
956
+
957
+ def register_rulesets(parent_name, ruleset_definitions)
958
+ rulesets = Ruleset.create_rulesets(parent_name, self, ruleset_definitions, @state_cache_size)
959
+ for ruleset_name, ruleset in rulesets do
960
+ if @ruleset_directory.key? ruleset_name
961
+ raise ArgumentError, "Ruleset with name #{ruleset_name} already registered"
962
+ end
963
+
964
+ @ruleset_directory[ruleset_name] = ruleset
965
+ @ruleset_list << ruleset
966
+ ruleset.bind @databases
967
+ end
968
+
969
+ rulesets.keys
970
+ end
971
+
972
+ def start!
973
+ index = 1
974
+ timers = Timers::Group.new
975
+
976
+ dispatch_ruleset = -> c {
977
+
978
+ callback = -> e {
979
+ puts "internal error #{e}" if e
980
+ if index % 10 == 0
981
+ index += 1
982
+ timers.after 0.01, &dispatch_ruleset
983
+ else
984
+ index += 1
985
+ dispatch_ruleset.call nil
986
+ end
987
+ }
988
+
989
+ timers_callback = -> e {
990
+ puts "internal error #{e}" if e
991
+ if index % 10 == 0 && @ruleset_list.length > 0
992
+ ruleset = @ruleset_list[(index / 10) % @ruleset_list.length]
993
+ ruleset.dispatch_timers callback
994
+ else
995
+ callback.call e
996
+ end
997
+ }
998
+
999
+ if @ruleset_list.length > 0
1000
+ ruleset = @ruleset_list[index % @ruleset_list.length]
1001
+ ruleset.dispatch timers_callback
1002
+ else
1003
+ timers_callback.call nil
1004
+ end
1005
+ }
1006
+
1007
+ timers.after 0.001, &dispatch_ruleset
1008
+ Thread.new do
1009
+ loop { timers.wait }
1010
+ end
1011
+ end
1012
+
1013
+ end
1014
+
1015
+ end