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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/Rakefile +34 -0
- data/deps/hiredis/COPYING +29 -0
- data/deps/hiredis/Makefile +148 -0
- data/deps/hiredis/async.c +622 -0
- data/deps/hiredis/async.h +125 -0
- data/deps/hiredis/dict.c +338 -0
- data/deps/hiredis/dict.h +126 -0
- data/deps/hiredis/fmacros.h +16 -0
- data/deps/hiredis/hiredis.c +1285 -0
- data/deps/hiredis/hiredis.h +210 -0
- data/deps/hiredis/net.c +299 -0
- data/deps/hiredis/net.h +47 -0
- data/deps/hiredis/sds.c +882 -0
- data/deps/hiredis/sds.h +100 -0
- data/deps/hiredis/test.c +654 -0
- data/deps/hiredis/zmalloc.h +13 -0
- data/librb/durable.rb +549 -0
- data/librb/engine.rb +1015 -0
- data/librb/interface.rb +64 -0
- data/src/rules/Makefile +55 -0
- data/src/rules/events.c +1848 -0
- data/src/rules/json.c +423 -0
- data/src/rules/json.h +24 -0
- data/src/rules/net.c +2559 -0
- data/src/rules/net.h +141 -0
- data/src/rules/rete.c +1726 -0
- data/src/rules/rete.h +133 -0
- data/src/rules/rules.h +171 -0
- data/src/rules/state.c +412 -0
- data/src/rules/state.h +57 -0
- data/src/rulesrb/extconf.rb +37 -0
- data/src/rulesrb/rules.c +644 -0
- metadata +120 -0
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
|