durable_rules 0.31.1

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/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