123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127open!CoremoduleIncr=Ui_incrmoduleEffect=Ui_effecttypet={incr:Incr.Clock.t;timing_wheel:(unit,unit)Ui_effect.Private.Callback.tTiming_wheel.t;mutableadd_before_advance:(Time_ns.t*(unit,unit)Ui_effect.Private.Callback.t)Reversed_list.t;mutablewait_after_display_callbacks:(unit,unit)Ui_effect.Private.Callback.tReversed_list.t;mutableadvance_to:Time_ns.toption}letinvariantt=(* This is only a soft invariant (it prints instead of raising) because it
probably isn't fatal if the two clocks are out of sync. We want to know
about it if they are, though. *)letwheel_now=Timing_wheel.nowt.timing_wheelinletincr_now=Incr.Clock.nowt.incrinifnot(Time_ns.equalwheel_nowincr_now)theneprint_s[%message"BUG: timing wheel and incremental clock are out of sync"(wheel_now:Time_ns.Alternate_sexp.t)(incr_now:Time_ns.Alternate_sexp.t)];;letcreate~start=letstart=(* We round the start time to match what [Incr.Clock.create] does. *)Time_ns.of_time_float_round_nearest_microsecond(Time_ns.to_time_float_round_nearest_microsecondstart)inletconfig=Incr.Clock.default_timing_wheel_configinletincr=Incr.Clock.create~timing_wheel_config:config~start()inlettiming_wheel=Timing_wheel.create~config~startinlett={incr;timing_wheel;add_before_advance=[];wait_after_display_callbacks=[];advance_to=None}ininvariantt;t;;letincr_clockt=t.incrletnowt=matcht.advance_towith|Someto_->to_|None->Timing_wheel.nowt.timing_wheel;;letat_intervalstspan=Incr.Clock.at_intervalst.incrspanletwatch_nowt=Incr.Clock.watch_nowt.incrletattat=Incr.Clock.att.incratletadvance_clockt~to_=assert(Time_ns.(>=)to_(nowt));t.advance_to<-Someto_;;letadvance_clock_bytspan=advance_clockt~to_:(Time_ns.add(nowt)span)(* [until], [sleep], and [wait_after_display] all want to add alarms to [t.timing_wheel],
which throws if we're already in the middle of an alarm. Instead of adding it to the
timing wheel immediately, we store it and then add it next time the timing wheel is
advanced.
Note: the alarms are added as part of [flush], which is the only place that we actually
advance [t.time_source], which makes this approach sound. *)letuntiltat=Effect.Private.make~request:()~evaluator:(funcallback->t.add_before_advance<-(at,callback)::t.add_before_advance);;letsleeptspan=Effect.Private.make~request:()~evaluator:(funcallback->letat=Time_ns.add(nowt)spanint.add_before_advance<-(at,callback)::t.add_before_advance);;letwait_after_displayt=Effect.Private.make~request:()~evaluator:(funcallback->t.wait_after_display_callbacks<-callback::t.wait_after_display_callbacks);;modulePrivate=structletflusht=lethandle_firedcallback=Effect.Expert.handle(Effect.Private.Callback.respond_to(Timing_wheel.Alarm.valuet.timing_wheelcallback)())inList.iter(Reversed_list.revt.add_before_advance)~f:(fun(at,callback)->let(_:_Timing_wheel.Alarm.t)=Timing_wheel.addt.timing_wheel~atcallbackin());t.add_before_advance<-[];(matcht.advance_towith|Someto_->t.advance_to<-None;Timing_wheel.advance_clockt.timing_wheel~to_~handle_fired;Timing_wheel.fire_past_alarmst.timing_wheel~handle_fired;Incr.Clock.advance_clockt.incr~to_|None->Timing_wheel.fire_past_alarmst.timing_wheel~handle_fired);invariantt;;lettrigger_after_displayt=letcallbacks=t.wait_after_display_callbacksint.wait_after_display_callbacks<-[];List.iter(Reversed_list.revcallbacks)~f:(funcallback->Effect.Expert.handle(Effect.Private.Callback.respond_tocallback()));;lethas_after_display_eventst=not(Reversed_list.is_emptyt.wait_after_display_callbacks);;end