UXEventRecorder
UXEventRecorder tees a copy of each event into a list, and replays them to a
sink.
#use <UXKit> // or #import "UXEventRecorder.xc"Overview
Section titled “Overview”UXEventRecorder* r = new UXEventRecorder();r.start(nowMs);// … the application runs; the event tap calls r.record(e, nowMs) …r.stop();
r.replay(&self.dispatch); // feed them back, in orderUse it for deterministic bug repros, demos, and “do that again”.
It taps a funnel that already exists
Section titled “It taps a funnel that already exists”The neutral run loop puts every event through one place. A recorder is a tee off that funnel and adds nothing to the dispatch path.
The toolkit therefore needs no special recording mode. Anything that reaches the application reaches the recorder, with no second code path that could behave differently.
No clock, no dispatch
Section titled “No clock, no dispatch”The recorder does not own two things:
- the clock: the caller passes
nowMsin, from the driver - the sink: the caller provides where replayed events go, which is the application’s own dispatch
The model is backend-neutral and testable with synthetic time. A test records at times it chose, replays into a sink it wrote, and asserts on the output, with no window, no driver and no waiting.
UXTimerScheduler uses the same
design, for the same reason.
Events are deep-copied
Section titled “Events are deep-copied”static UXEvent* copyEvent(UXEvent* e)Drivers usually reuse one UXEvent object
for every event they announce, filling in the fields and dispatching, because an
allocation per mouse-move is too expensive.
A recorder storing pointers would end up with a thousand references to one object holding whatever arrived last. Copying keeps each recorded event intact, and lets a tape captured under AppKit replay with no AppKit present.
Replay does not sleep
Section titled “Replay does not sleep”void replay(callback sink void(UXEvent* e))void replayRange(callback sink void(UXEvent* e), i32 fromMs, i32 toMs)Events are fed to the sink in order, as fast as the loop runs. Timing is recorded, not enforced.
The caller decides the replay speed: instant for a test, real-time for a demo, one at a time for a debugging session. The recorder does not choose, because the three cases need different things.
replayRange makes a long recording useful. A bug 40 seconds
into a session is reproduced by replaying 38 to 42, without sitting through the
rest.
Recording is a switch, not a lifetime
Section titled “Recording is a switch, not a lifetime”r.start(nowMs); // clears the tape and beginsr.stop(); // stops; the tape is keptr.isRecording();start clears the tape, so it begins a new recording and does not
resume. stop keeps the events, so the tape is available to replay or inspect
afterwards. clear discards it.
A record call while not recording is ignored. The tap can stay installed
permanently and costs one boolean test when nothing is being recorded.
Topics
Section titled “Topics”start · stop · isRecording · record · count · eventAt · timeAt · durationMs · clear · replay · replayRange
void start(i32 nowMs)Clears the tape, begins recording, and takes nowMs as time zero. Every
recorded timestamp is relative to it.
void stop(void)isRecording
Section titled “isRecording”bool isRecording(void)record
Section titled “record”void record(UXEvent* e, i32 nowMs)Called by the tap. Copies the event and stamps it. A no-op when not recording.
i32 count(void)eventAt
Section titled “eventAt”UXEvent* eventAt(i32 i)One recorded event, unwrapped from its
UXRecordedEvent, for asserting on
what arrived without replaying it.
timeAt
Section titled “timeAt”i32 timeAt(i32 i)That event’s offset in milliseconds from the start of recording. Together with
eventAt, it lets you inspect a recording without the boxing type.
durationMs
Section titled “durationMs”i32 durationMs(void)The last event’s timestamp, which is how long the recording spans. 0 for an
empty tape.
Use it to choose a replayRange window, or as the length a demo
paces against.
void clear(void)replay
Section titled “replay”void replay(callback sink void(UXEvent* e))Replays every event, in order.
replayRange
Section titled “replayRange”void replayRange(callback sink void(UXEvent* e), i32 fromMs, i32 toMs)Replays only the events whose timestamps fall in the window.
See also
Section titled “See also”UXRecordedEvent: one entry, and why the copy mattersUXEvent: what is being recorded, and why it is reusedUXTimerScheduler: the same no-clock-of-its-own design