-
Notifications
You must be signed in to change notification settings - Fork 1
/
dyn_traffic_light.cr
171 lines (145 loc) · 4.08 KB
/
dyn_traffic_light.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
require "../src/quartz"
class TrafficLight < Quartz::AtomicModel
input :interrupt
output :observed
state do
var phase : Symbol = :red
end
def external_transition(messages)
value = messages[input_ports[:interrupt]].first.as_sym
self.phase = case value
when :to_manual
:manual
else # :to_autonomous
:red
end
end
def internal_transition
self.phase = case phase
when :red
:green
when :green
:orange
else # orange
:red
end
end
def output
observed = case phase
when :red, :orange
:grey
when :green
:orange
else
raise "BUG: unreachable"
end
post observed, :observed
end
def time_advance : Quartz::Duration
case phase
when :red then Quartz.duration(60)
when :green then Quartz.duration(50)
when :orange then Quartz.duration(10)
else # manual
Quartz::Duration::INFINITY
end
end
end
class Policeman < Quartz::AtomicModel
state do
var phase : Symbol = :idle1
end
output :alternate, :add_coupling, :remove_coupling
def external_transition(bag)
end
def internal_transition
self.phase = case phase
when :idle1 then :working1
when :working1 then :move1_2
when :move1_2 then :idle2
when :idle2 then :working2
when :working2 then :move2_1
else # move2_1
:idle1
end
end
def output
case phase
when :idle1, :idle2
post :to_manual, :alternate
when :working1, :working2
post :to_autonomous, :alternate
else
tl1 = Quartz::Any.hash.tap do |h|
h[:src] = :policeman
h[:dst] = :traffic_light1
h[:src_port] = :alternate
h[:dst_port] = :interrupt
end
tl2 = tl1.dup
tl2[:dst] = :traffic_light2
if phase == :move1_2
post(tl1, :remove_coupling)
post(tl2, :add_coupling)
else # move2_1
post(tl2, :remove_coupling)
post(tl1, :add_coupling)
end
end
end
def time_advance : Quartz::Duration
case phase
when :idle1, :idle2
Quartz.duration(50)
when :working1, :working2
Quartz.duration(100)
when :move2_1, :move1_2
Quartz.duration(150)
else
Quartz::Duration::INFINITY
end
end
end
class Grapher
include Quartz::Observer
def initialize(model, @simulation : Quartz::Simulation)
model.add_observer(self)
end
def update(model, info)
if model.is_a?(Quartz::DSDE::Executive) && info
kind = info[:transition]
if kind == :internal || kind == :confluent
@simulation.generate_graph("dyntrafficlight_#{@simulation.virtual_time.to_s}")
end
end
end
end
class PortObserver
include Quartz::Observer
def initialize(port)
port.add_observer(self)
end
def update(observable, info)
if observable.is_a?(Quartz::Port) && info
payload = info[:payload]
time = info[:time].as(Quartz::TimePoint)
puts "#{observable.host}@#{observable} sends '#{payload}' at #{time.to_s}"
end
end
end
model = Quartz::DSDE::CoupledModel.new(:dynamic_crossroads)
tl1 = TrafficLight.new(:traffic_light1)
tl2 = TrafficLight.new(:traffic_light2)
policeman = Policeman.new(:policeman)
model << policeman
model << tl1
model << tl2
model.attach :add_coupling, to: :add_coupling, between: :policeman, and: :executive
model.attach :remove_coupling, to: :remove_coupling, between: :policeman, and: :executive
model.attach :alternate, to: :interrupt, between: :policeman, and: :traffic_light1
simulation = Quartz::Simulation.new(model, duration: Quartz.duration(1000), scheduler: :binary_heap)
simulation.generate_graph("dyntrafficlight_0")
Grapher.new(model.executive, simulation)
PortObserver.new(tl1.output_port(:observed))
PortObserver.new(tl2.output_port(:observed))
simulation.simulate