123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128(** Execute actions after a specified timeout *)(* Warning: the Timemout by itself will not generate any event. Therefore, if
the action needs an immediate redraw by the main loop, the redraw event
should be triggered, or the action_event (if just breaking the wait_event
loop is enough). TODO ? Maybe it's better that all timeouts break the the
wait_event loop? *)(* we need an ordered data structure, with very fast folding ( = itering in
increasing order), but the insertion time is not a problem. *)(* We chose here an ordered List. Maybe that's not optimal. *)moduleUtils=B_utilsmoduleTime=B_timemoduleVar=B_vartypeaction=unit->unit;;typet={id:int;timeout:Time.t;action:action}letnew_id=Utils.fresh_int();;letcreatetimeoutaction={id=new_id();timeout;action};;letexecutet=ifTime.(now()>>t.timeout)then(Utils.(printddebug_board"Executing timeout");t.action();true)elsefalse;;(* the global stack variable *)letstack=Var.create[];;letclear()=ifVar.getstack<>[]thenbeginUtils.(printddebug_warning"Clearing the remaining %u Timeouts"(List.length(Var.getstack)));Var.setstack[]end;;(* insert t at the right place in list *)letinserttlist=letrecloopbefore_revafter=matchafterwith|[]->List.rev(t::before_rev)|a::rest->ifa.timeout>t.timeoutthenList.rev_appendbefore_rev(t::after)elseloop(a::before_rev)restinloop[]list;;(* insert a sublist in a list *)(* it should be slightly more efficient than using "insert" repeatedly *)(* because once an element of sublist in inserted into list, we know the other
elements of sublist will fall on the right of it. *)letinsert_sublistsublistlist=ifsublist=[]thenlistelsebeginletrecloopsubfinal_revrest=letrecinserttbefore_revafter=matchafterwith|[]->(t::before_rev),[]|a::rest->ifa.timeout>t.timeoutthen(t::before_rev),afterelseinsertt(a::before_rev)restinmatchsubwith|[]->List.rev_appendfinal_revrest|t::subrest->letbefore_rev,after=inserttfinal_revlistinloopsubrestbefore_revafterinloopsublist[]listend;;(* Immediately registers a new timeout and returns it. In general it's better to
use push in order to get a correct starting time, unless we know this is done
dynamically during the main loop. *)letaddtimeoutaction=lettimeout=Time.now()+timeoutinlett=createtimeoutactioninVar.protect_fnstack(fun()->letlist=Var.unsafe_getstackinVar.unsafe_setstack(inserttlist));t;;(* Push a timeout to be registered at the next iteration of the main loop. *)letpushtimeoutaction=(fun()->addtimeoutaction)|>Stack.pushletnot_equalt1t2=t1.id<>t2.id;;(* remove a Timeout from stack *)letremovetstack=Var.protect_fnstack(fun()->letlist=Var.unsafe_getstackinVar.unsafe_setstack(List.filter(not_equalt)list));;(** cancel a Timeout from the global stack *)letcancelt=removetstack;;letiterstack=(* we pop the whole list and push back an empty stack in case some thread want
to add new timeouts while we are processing *)letlist=Var.protect_fnstack(fun()->letlist=Var.unsafe_getstackinVar.unsafe_setstack[];list)inletrecloopl=matchlwith|[]->[]|t::l'->ifexecutetthenloopl'elsel(* the action t was not executed, we leave it in the stack *)inletremaining=looplistinVar.protect_fnstack(fun()->letmodified=Var.unsafe_getstackinVar.unsafe_setstack(insert_sublistmodifiedremaining));;letrun()=(* the stack should be empty most of the time, so we add a test to be faster *)ifVar.getstack<>[]theniterstack;;