samer@2
|
1 % rsched - regular state threading scheduler
|
samer@0
|
2 %
|
samer@0
|
3 % rsched ::
|
samer@2
|
4 % timed_action({nonneg,S},{S}) ~'timed action from current period and state to state',
|
samer@0
|
5 % S ~'first state',
|
samer@2
|
6 % nonneg ~'timer period',
|
samer@2
|
7 % time ~'start time',
|
samer@0
|
8 % options {
|
samer@0
|
9 % its :: natural/inf ~'iteration limit';
|
samer@0
|
10 % defer :: bool/0 ~ 'if true, do not start timer';
|
samer@0
|
11 % error :: real/0 ~ 'estimate of timing error on timer start';
|
samer@0
|
12 % final :: bool/0 ~ 'make final state available?';
|
samer@0
|
13 %
|
samer@0
|
14 % onstart :: (
|
samer@0
|
15 % S ~'current state',
|
samer@6
|
16 % real ~'scheduled start time',
|
samer@6
|
17 % datenum ~'actual start date-stamp'
|
samer@2
|
18 % => void)/@nop ~ 'called on timer start';
|
samer@0
|
19 %
|
samer@0
|
20 % onstop :: (
|
samer@0
|
21 % S ~'current state',
|
samer@6
|
22 % datenum ~'actual start date-stamp'
|
samer@6
|
23 % => void)/@nop ~ 'called on timer stop';
|
samer@0
|
24 %
|
samer@0
|
25 % onfinish:: (
|
samer@6
|
26 % datenum ~'actual start date-stamp'
|
samer@2
|
27 % => void)/@nop ~ 'called if state seq finishes';
|
samer@0
|
28 %
|
samer@0
|
29 % busy_mode :: {'queue','drop'};
|
samer@0
|
30 % exec_mode :: {'fixedRate','fixedDelay','fixedSpacing'}
|
samer@0
|
31 % }
|
samer@2
|
32 % => struct {
|
samer@0
|
33 % timer :: timer ~'timer being used';
|
samer@2
|
34 % startat :: (double => void) ~'start timer at given time';
|
samer@2
|
35 % start :: (void => void) ~'start timer asap';
|
samer@2
|
36 % stop :: (void => void) ~'stop timer';
|
samer@2
|
37 % rewind :: (void => void) ~'rewind to first state';
|
samer@2
|
38 % trim :: (double => double)~'trim timing errors';
|
samer@2
|
39 % dispose :: (void => void) ~'stop timer';
|
samer@2
|
40 % getstate :: (void => S) ~'current state';
|
samer@2
|
41 % setstate :: (S => void) ~'set state (only when stopped)';
|
samer@2
|
42 % isrunning:: (void => bool) ~'true if scheduler is running';
|
samer@2
|
43 % wait :: (void => void) ~'wait for sched to stop'
|
samer@0
|
44 % }.
|
samer@0
|
45 %
|
samer@0
|
46 % NB: switching on final state availability option 'final' can make
|
samer@0
|
47 % timer callbacks much slower due to requirement for copying state.
|
samer@0
|
48
|
samer@2
|
49 function api=rsched(schedfn,S0,dt,T0,varargin)
|
samer@0
|
50
|
samer@0
|
51 warning('off','MATLAB:TIMER:STARTDELAYPRECISION');
|
samer@0
|
52 warning('off','MATLAB:TIMER:RATEPRECISION');
|
samer@0
|
53
|
samer@0
|
54 opts=prefs('defer',0,'its',inf,'final',0, ...
|
samer@0
|
55 'onstart',@nop,'onstop',@nop,'onfinish',@nop, ...
|
samer@0
|
56 'busy_mode','queue','exec_mode','fixedRate',varargin{:});
|
samer@0
|
57
|
samer@0
|
58 % Implementation note: it's MUCH faster to use a local variable
|
samer@0
|
59 % as mutable state instead of using the timer getter and setter.
|
samer@2
|
60 State=S0; SchedStart=T0; FinalState=[];
|
samer@0
|
61 STARTERR=getparam(opts,'error',0);
|
samer@0
|
62 DT=dt;
|
samer@0
|
63
|
samer@0
|
64 if opts.final, timfn=@adv; else timfn=@adv2; end
|
samer@0
|
65 Timer=timer('ExecutionMode',opts.exec_mode,'BusyMode',opts.busy_mode, ...
|
samer@0
|
66 'StartFcn',@startfn,'StopFcn',@stopfn,'TimerFcn',timfn, ...
|
samer@0
|
67 'Period',DT,'TasksToExecute',opts.its);
|
samer@0
|
68
|
samer@0
|
69 api = struct(...
|
samer@0
|
70 'dispose', @()delete(Timer), ...
|
samer@0
|
71 'isrunning',@()isrunning(Timer), ...
|
samer@0
|
72 'startat', @startat, ...
|
samer@0
|
73 'start', @startnow, ...
|
samer@0
|
74 'stop', @()stop(Timer), ...
|
samer@0
|
75 'rewind', @()setstate(S0), ...
|
samer@0
|
76 'getstate', @getstate, ...
|
samer@0
|
77 'setstate', @setstate, ...
|
samer@0
|
78 'trim', @trim, ...
|
samer@6
|
79 'timer', @()Timer, ...
|
samer@6
|
80 'wait', @sched_wait ...
|
samer@0
|
81 );
|
samer@0
|
82
|
samer@2
|
83 if opts.final, api.finalstate=@()FinalState; end
|
samer@0
|
84 if ~opts.defer, startat(T0); end
|
samer@0
|
85
|
samer@0
|
86 function check(msg), if isrunning(Timer), error(msg); end; end
|
samer@0
|
87 function err=trim(derr), STARTERR=STARTERR+derr; err=STARTERR; end
|
samer@2
|
88 function s=getstate, s=State; end
|
samer@0
|
89 function setstate(s),
|
samer@0
|
90 check('Cannot set state of running scheduler');
|
samer@2
|
91 State=s;
|
samer@0
|
92 end
|
samer@0
|
93
|
samer@0
|
94 function startat(t0) % !! what if timer is already running?
|
samer@0
|
95 check('Timer is already running');
|
samer@2
|
96 SchedStart=t0;
|
samer@0
|
97 start_delay = t0-nows-STARTERR
|
samer@0
|
98 if start_delay<0
|
samer@0
|
99 fprintf('\n| WARNING: start delay=%f, starting now.',start_delay);
|
samer@0
|
100 start_delay=0;
|
samer@0
|
101 end
|
samer@0
|
102 set(Timer,'StartDelay',start_delay);
|
samer@0
|
103 start(Timer);
|
samer@0
|
104 end
|
samer@0
|
105
|
samer@0
|
106 function startnow % !! what if timer is already running?
|
samer@0
|
107 check('Timer is already running');
|
samer@2
|
108 SchedStart=nows+STARTERR;
|
samer@0
|
109 set(Timer,'StartDelay',0);
|
samer@0
|
110 start(Timer);
|
samer@0
|
111 end
|
samer@0
|
112
|
samer@0
|
113 function stopfn(o,e),
|
samer@2
|
114 opts.onstop(State,e.Data.time);
|
samer@6
|
115 if isempty(State), opts.onfinish(e.Data.time); end
|
samer@0
|
116 end
|
samer@0
|
117
|
samer@0
|
118 function startfn(o,e)
|
samer@0
|
119 DT=get(o,'Period'); % use current period in timer callback
|
samer@2
|
120 opts.onstart(State,SchedStart,e.Data.time);
|
samer@0
|
121 end
|
samer@0
|
122
|
samer@0
|
123 % if final state not required we can use faster assign in place
|
samer@0
|
124 function adv2(o,e)
|
samer@2
|
125 [err,State]=schedfn(SchedStart,DT,State);
|
samer@2
|
126 if isempty(State), stop(o); end
|
samer@2
|
127 SchedStart=SchedStart+DT;
|
samer@0
|
128 end
|
samer@0
|
129
|
samer@0
|
130 % final state required
|
samer@0
|
131 function adv(o,e)
|
samer@2
|
132 [err,s1]=schedfn(SchedStart,DT,State);
|
samer@2
|
133 if isempty(s1), FinalState=State; stop(o); end
|
samer@2
|
134 SchedStart=SchedStart+DT;
|
samer@2
|
135 State=s1;
|
samer@2
|
136 end
|
samer@0
|
137
|
samer@2
|
138 function sched_wait
|
samer@2
|
139 while Timer.isrunning(); pause(0.05); end
|
samer@0
|
140 end
|
samer@6
|
141 end
|