samer@3: :- module(recorder, samer@3: [ recorder/2 samer@3: , save_events/1 samer@3: , load_events/1 samer@4: , get_events/1 samer@3: , player/2 samer@3: , player/3 samer@3: ]). samer@0: /** event recording samer@0: samer@0: This module provides a way to capture and record events processed by samer@0: the reactive programming framework of reactive.pl. samer@0: */ samer@3: :- meta_predicate recorder(1,?), player(1,?), player(2,2,?). samer@0: :- dynamic start_time/1, event/2. samer@0: samer@0: :- use_module(library(fileutils)). samer@12: :- use_module(library(utils),[min/3]). samer@0: :- use_module(reactive). samer@0: samer@0: samer@0: %% recorder( +Client:ptail, -Proc:process) is det. samer@0: % samer@0: % This predicate represents a reactive process that behaves as Client, samer@0: % but records all events in the Prolog dynamic database. The type signature samer@0: % implies that the term recorder(Client) is of type =|ptail|=. All previously samer@0: % recorded events are deleted first. The time at which this predicate was samer@0: % called is also recorded in the database. samer@0: samer@0: recorder(Client,Proc) :- samer@0: get_time(Now), samer@0: retractall(event(_,_)), samer@0: retractall(start_time(_)), samer@0: assert(start_time(Now)), samer@1: format('recording events for ~w.\n',[Client]), samer@0: call(Client,C1), samer@0: recorder_cont(C1,Proc). samer@0: samer@0: recorder_on_event(E,C1,Proc) :- samer@0: assert(E), samer@0: step_event(E,C1,C2), samer@0: recorder_cont(C2,Proc). samer@0: samer@0: recorder_on_timeout(T,C1,Proc) :- samer@0: step_timeout(T,C1,C2), samer@0: recorder_cont(C2,Proc). samer@0: samer@0: recorder_cont(C1,Proc) :- samer@0: get_timeout(C1,T1), samer@0: ( T1=inf samer@0: -> req_message(E^recorder_on_event(E,C1), Proc) samer@0: ; req_message_or_timeout(T1, samer@0: recorder_on_timeout(T1,C1), samer@0: E^recorder_on_event(E,C1), Proc) samer@0: ). samer@0: samer@3: %% player( +Client:ptail, +Trans:pred(A,A), -Proc:process) is det. samer@0: %% player( +Client:ptail, -Proc:process) is det. samer@0: % samer@0: % This predicate represents a reactive process that behaves as Client, samer@0: % but plays back events in the Prolog dynamic database. The events are samer@0: % time shifted by the difference between the recorded start time and the samer@0: % time at which player/2 is called. The type signature samer@0: % implies that the term recorder(Client) is of type =|ptail|=. samer@3: % samer@3: % player/3 allows an event transformer pred(+EventIn:A,-EventOut:A) to be samer@3: % specified. player/2 is equivalent to using=/2 as the transformer. samer@0: samer@0: player(Client,Proc) :- samer@0: get_time(Now), samer@0: start_time(T0), DT is Now-T0, samer@0: setof(event(T,Msg),T1^(event(T1,Msg),T is DT+T1),Events), samer@0: call(Client,C1), samer@0: player_cont(Events,C1,Proc). samer@0: samer@3: player(Client,Trans,Proc) :- samer@3: get_time(Now), samer@3: start_time(T0), DT is Now-T0, samer@3: setof(event(T,Msg),T1^Msg1^(event(T1,Msg1),T is DT+T1, call(Trans,Msg1,Msg)),Events), samer@3: call(Client,C1), samer@3: player_cont(Events,C1,Proc). samer@3: samer@0: player_on_event(Events,C1,Proc) :- samer@0: player_cont(Events,C1,Proc). samer@0: samer@0: player_on_timeout(T,ET,Events,C1,Proc) :- samer@0: ( T step_timeout(T,C1,C2), samer@0: player_cont(Events,C2,Proc) samer@0: ; Events=[E1|EX], samer@0: step_event(E1,C1,C2), samer@0: player_cont(EX,C2,Proc) samer@0: ). samer@0: samer@0: player_cont([],C1,C1) :- !, samer@0: writeln('Playback finished - continuing in interactive mode'). samer@0: samer@0: player_cont(Events,C1,Proc) :- samer@0: Events=[event(ET,_)|_], samer@0: get_timeout(C1,T1), samer@0: min(ET,T1,TO), samer@0: samer@0: req_message_or_timeout(TO, samer@0: player_on_timeout(TO,ET,Events,C1), samer@0: _^player_on_event(Events,C1), Proc). samer@0: samer@0: samer@0: %% save_events(+FileName:atom) is det. samer@0: % Save events in the event database to the named file (as Prolog clauses). samer@0: save_events(File) :- samer@0: with_output_to_file(File,(listing(start_time),listing(event))). samer@0: samer@0: samer@0: %% load_events(+FileName:atom) is det. samer@0: % Load events from the named file to the event database, after removing samer@0: % any events currently in there. samer@0: load_events(File) :- samer@0: retractall(event(_,_)), samer@0: retractall(start_time(_)), samer@0: consult(File). samer@0: samer@4: samer@4: %% get_events(-Events:list(event)) is det. samer@4: % Gets the currently loaded events as a list. Event times are relative samer@4: % to start time. samer@4: get_events(Events) :- samer@4: start_time(T0), samer@4: setof(event(T,Msg),T1^(event(T1,Msg),T is T1-T0),Events).