samer@0
|
1 :- module(reactive,
|
samer@0
|
2 [ run_process/1
|
samer@0
|
3 , init_process/2
|
samer@0
|
4 , step_event/3
|
samer@0
|
5 , step_timeout/2
|
samer@0
|
6 , step_timeout/3
|
samer@0
|
7 , get_timeout/2
|
samer@0
|
8 , req_message/2
|
samer@0
|
9 , req_message_or_timeout/4
|
samer@0
|
10 , (>)/3
|
samer@0
|
11 ]).
|
samer@0
|
12
|
samer@0
|
13 /** <module> Tools for reactive programming
|
samer@0
|
14
|
samer@0
|
15 This module provides a framework for doing
|
samer@0
|
16 reactive programming, where a reactive process is
|
samer@0
|
17 represented by one or two continuations
|
samer@0
|
18 which define how it will react to received events or the
|
samer@0
|
19 passage of time.
|
samer@0
|
20
|
samer@0
|
21 Relevant types:
|
samer@0
|
22 ==
|
samer@0
|
23 ptail == pred(process).
|
samer@0
|
24
|
samer@0
|
25 process ---> get(term,ptail)
|
samer@0
|
26 ; gett(time,term,ptail,ptail).
|
samer@0
|
27
|
samer@0
|
28
|
samer@0
|
29 event ---> event(time,_).
|
samer@0
|
30
|
samer@0
|
31 event_handler ---> term^ptail.
|
samer@0
|
32 ==
|
samer@0
|
33
|
samer@0
|
34 The type =|ptail|= represents a slice of execution for a process.
|
samer@0
|
35 When called, code may execute until the process
|
samer@0
|
36 wishes to block waiting for an event or time-out to occur.
|
samer@0
|
37 It does this by returning a value of type =|process|=
|
samer@0
|
38 representing either a process waiting indefinitely for an event,
|
samer@0
|
39 or a processing waiting until a certain for an event.
|
samer@0
|
40
|
samer@0
|
41 Values of type time are either floats representing times as
|
samer@0
|
42 returned by get_time/1 or the atom 'inf' representing a point
|
samer@0
|
43 infinitely far in the future.
|
samer@0
|
44 */
|
samer@0
|
45
|
samer@0
|
46 :- meta_predicate req_message(:,-).
|
samer@0
|
47 :- meta_predicate req_message_or_timeout(+,:,:,-).
|
samer@0
|
48 :- meta_predicate run_process(1).
|
samer@0
|
49 :- meta_predicate init_process(1,?).
|
samer@0
|
50 :- meta_predicate >(+,1,?).
|
samer@0
|
51
|
samer@0
|
52 :- use_module(qutils).
|
samer@0
|
53
|
samer@0
|
54 %---------------------------------------
|
samer@0
|
55
|
samer@0
|
56 %% init_process( +Spec:ptail, -P:process) is det.
|
samer@0
|
57 %
|
samer@0
|
58 % Initialise process specified by Spec, which is
|
samer@0
|
59 % simply a callable term taking one argument.
|
samer@0
|
60 % P is bound to representation of sleeping
|
samer@0
|
61 % process on exit.
|
samer@0
|
62 init_process(PS,P1) :- call(PS,P1).
|
samer@0
|
63
|
samer@0
|
64 %% >(+E:event,+Spec:ptail,-P:process) is det.
|
samer@0
|
65 %
|
samer@0
|
66 % Initialise process and immediately supply an event.
|
samer@0
|
67 % This uses init_process/2 to initialise the process specified
|
samer@0
|
68 % by Spec and then use step_event/3 to process the given event.
|
samer@0
|
69 %
|
samer@0
|
70 % This implies that if Spec is of type ptail, then
|
samer@0
|
71 % Event>Spec is also of type ptail.
|
samer@0
|
72 >(Event,Spec,Proc) :-
|
samer@0
|
73 init_process(Spec,P1),
|
samer@0
|
74 step_event(Event,P1,Proc).
|
samer@0
|
75
|
samer@0
|
76 %% run_process( +Spec:ptail) is det.
|
samer@0
|
77 %
|
samer@0
|
78 % Runs process represented by Spec (see init_process/2).
|
samer@0
|
79 % After initialising, this predicate goes into a recursive procedure
|
samer@0
|
80 % waiting for events on the current thread's message queue and
|
samer@0
|
81 % passing them to the waiting process.
|
samer@0
|
82 run_process(M:Spec) :- init_process(M:Spec,Proc), cont_process(Proc).
|
samer@0
|
83
|
samer@0
|
84
|
samer@0
|
85 %% cont_process( +Proc:process) is det.
|
samer@0
|
86 %
|
samer@0
|
87 % Takes a quiescent process, waits for and then handles a new event.
|
samer@0
|
88 % (Including timeout events.)
|
samer@0
|
89 cont_process(Proc) :-
|
samer@0
|
90 get_timeout(Proc,T), !,
|
samer@0
|
91 garbage_collect_atoms,
|
samer@0
|
92 get_message_or_timeout(T,Event),
|
samer@0
|
93 event_process_cont(Event,Proc,T).
|
samer@0
|
94
|
samer@0
|
95
|
samer@0
|
96 %% event_process_cont(+Event:evdesc,+P:process,+Deadline:time) is det.
|
samer@0
|
97 %
|
samer@0
|
98 % Handle latest event Event on process Proc. The evdesc type is defined
|
samer@0
|
99 % as =|evdesc ---> quit; timeout; event(time,term)|= with the following
|
samer@0
|
100 % semantics:
|
samer@0
|
101 %
|
samer@0
|
102 % * quit
|
samer@0
|
103 % The process will terminate: this procedure succeeds and returns immediately.
|
samer@0
|
104 %
|
samer@0
|
105 % * timeout
|
samer@0
|
106 % The timeout continuation of the process is followed.
|
samer@0
|
107 %
|
samer@0
|
108 % * event(Time,Data)
|
samer@0
|
109 % If the current time is less than 0.5s after the event time,
|
samer@0
|
110 % the process's event continuation is called. Otherwise,
|
samer@0
|
111 % the event is dropped and we continue as if it had never happened.
|
samer@0
|
112 event_process_cont(quit,_,_) :- !.
|
samer@0
|
113 event_process_cont(timeout,P1,_) :- step_timeout(P1,P2), !, cont_process(P2).
|
samer@0
|
114 event_process_cont(event(ET,EData),P1,T1) :- !,
|
samer@0
|
115 get_time(Now),
|
samer@0
|
116 ( Now>ET+0.5 -> writeln('#'), cont_process(P1)
|
samer@0
|
117 ; cont_event(T1,P1,ET,EData)).
|
samer@0
|
118
|
samer@0
|
119 %% cont_event(+Deadline:time, +P:process, +EvTime:time, +EvData:term) is det.
|
samer@0
|
120 %
|
samer@0
|
121 % Handles process continuation after receiving a valid event.
|
samer@0
|
122 % If the event is later than the process timeout, the timeout
|
samer@0
|
123 % continuation is called recursively until the process is ready
|
samer@0
|
124 % to receive the current event. Then this event is handled and
|
samer@0
|
125 % the process continues from there.
|
samer@0
|
126 cont_event(T1,P1,ET,EData) :-
|
samer@0
|
127 ( T1\=inf, ET>T1
|
samer@0
|
128 -> step_timeout(P1,P2),
|
samer@0
|
129 get_timeout(P2,T2), !,
|
samer@0
|
130 cont_event(T2,P2,ET,EData)
|
samer@0
|
131 ; step_event(event(ET,EData),P1,P2), !,
|
samer@0
|
132 cont_process(P2)
|
samer@0
|
133 ).
|
samer@0
|
134
|
samer@0
|
135 cont_event_safely(T1,P1,ET,EData) :-
|
samer@0
|
136 ( T1\=inf, ET>T1
|
samer@0
|
137 -> step_timeout(P1,P2),
|
samer@0
|
138 get_timeout(P2,T2), !,
|
samer@0
|
139 cont_event_safely(T2,P2,ET,EData)
|
samer@0
|
140 ; ( step_event(event(ET,EData),P1,P2) -> true
|
samer@0
|
141 ; writeln(failed(step_event(event(ET,EData),P1,P2))), P1=P2
|
samer@0
|
142 ),
|
samer@0
|
143 cont_process(P2)
|
samer@0
|
144 ).
|
samer@0
|
145
|
samer@0
|
146 %% req_message( +EventHandler:event_handler, -P:process) is det.
|
samer@0
|
147 %
|
samer@0
|
148 % Returns process state representing quiescent process
|
samer@0
|
149 % waiting indefinitley for event. EventHandler must be
|
samer@0
|
150 % a term of the form Msg^PTail, where Msg is to be unified
|
samer@0
|
151 % with next event term before calling PTail.
|
samer@0
|
152 req_message(Mod:Temp^OnEvent, get(Temp,Mod:OnEvent)).
|
samer@0
|
153
|
samer@0
|
154 %% req_message_or_timeout( +T:time, +TimeOutHandler:ptail, +EventHandler:event_handler, -P:process) is det.
|
samer@0
|
155 %
|
samer@0
|
156 % Returns process state representing quiescent process
|
samer@0
|
157 % waiting for event. EventHandler must be
|
samer@0
|
158 % a term of the form Msg^PTail, where Msg is to be unified
|
samer@0
|
159 % with next event term before calling PTail to get the next
|
samer@0
|
160 % continuation of the process.
|
samer@0
|
161 % TimeOutHandler will be called if time no event arrives
|
samer@0
|
162 % before time T.
|
samer@0
|
163 req_message_or_timeout(T,OnTimeout,Mod:Temp^OnEvent,
|
samer@0
|
164 gett(T,Temp,Mod:OnEvent,OnTimeout)).
|
samer@0
|
165
|
samer@0
|
166 leq(inf,T2) :- !, T2=inf.
|
samer@0
|
167 leq(T1,T2) :- !, (T2=inf -> true; T1=<T2).
|
samer@0
|
168
|
samer@0
|
169 %% get_timeout(+P:process,-Deadline:time) is det
|
samer@0
|
170 %
|
samer@0
|
171 % Get timeout time of quiescent process. May be inf.
|
samer@0
|
172 get_timeout(get(_,_),inf).
|
samer@0
|
173 get_timeout(gett(T,_,_,_),T).
|
samer@0
|
174
|
samer@0
|
175 %% step_timeout(+P1:process,-P2:process) is det.
|
samer@0
|
176 %
|
samer@0
|
177 % Wake up process P1 with timeout event and run until
|
samer@0
|
178 % it blocks at P2.
|
samer@0
|
179 step_timeout(gett(_,_,_,OnTimeout),P2) :- call(OnTimeout,P2).
|
samer@0
|
180 step_timeout(get(E,OnEvent),get(E,OnEvent)).
|
samer@0
|
181
|
samer@0
|
182 %% step_timeout(+T:time, +P1:process,-P2:process) is det.
|
samer@0
|
183 %
|
samer@0
|
184 % If time T is later than P1's timeout time, wake it up and
|
samer@0
|
185 % run timeout event until it blocks at P2. Otherwise, P2=P1,
|
samer@0
|
186 % ie process is still waiting.
|
samer@0
|
187 step_timeout(T,P1,P2) :-
|
samer@0
|
188 get_timeout(P1,T1),
|
samer@0
|
189 (leq(T1,T) -> step_timeout(P1,P2); P1=P2).
|
samer@0
|
190
|
samer@3
|
191 %:- index(step_event(0,1,0)).
|
samer@0
|
192
|
samer@0
|
193 %% step_event(+E:event, +P1:process, -P2:process) is det.
|
samer@0
|
194 %
|
samer@0
|
195 % Wake up process P1 with event E and run until blocking at P2.
|
samer@0
|
196 step_event(E,get(E,OnEvent),P2) :- call(OnEvent,P2).
|
samer@0
|
197 step_event(E,gett(_,E,OnEvent,_),P2) :- call(OnEvent,P2).
|