123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128(* This file is part of BOGUE, by San Vu Ngoc *)(* Execute actions after a specified timeout *)(* Warning: the Timeout 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->unittypet={id:int;timeout:Time.t;(* in absolute time units *)action:action;mutablecancelled:bool}letnew_id=Utils.fresh_int()letiterating=reffalse(* for debugging *)letcreatetimeoutaction={id=new_id();timeout;action;cancelled=false}letexecutet=if!Utils.debugthenassert(nott.cancelled);ifTime.(now()>>t.timeout)then(Utils.(printddebug_board"Executing timeout %i"t.id);t.action();true)elsefalse(* The global stack variable. It should always be sorted by final time of
execution. *)letstack=Var.create[](* (Not used) Should not be called while iterating... *)letclear()=if!Utils.debugthenassert(not!iterating);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. *)letinsertlistt=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 could certainly be optimised taking into account that lists are ordered: *)(* 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=List.fold_leftinsertlistsublist(* 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. *)letadddelayaction=lettimeout=Time.now()+delayinlett=createtimeoutactioninUtils.(printddebug_board"Adding timeout %i"t.id);Var.updatestack(funlist->(insertlistt));tletnot_equalt1t2=t1.id<>t2.id(* (Not used) Remove a Timeout from stack. Should not be called while
iterating. *)letremove_oldtstack=Var.updatestack(funlist->List.filter(not_equalt)list)(* Cancel a Timeout from the global stack. It will not be executed and will be
effectively removed from the stack by the next call to [iter]. *)letcancelt=Utils.(printddebug_board"Cancelling Timeout %i"t.id);t.cancelled<-trueletiterstack=(* We pop the whole list and push back an empty stack in case the actions in
the list, or some other thread, want to add new timeouts while we are
processing. *)letlist=Var.with_protectstack(funlist->Var.unsafe_setstack[];list)in(* Utils.(printd debug_custom "Iter timeout stack of size %i" (List.length
list)); *)letrecloopl=matchlwith|[]->[]|t::l'->ift.cancelled||executetthenloopl'elsel(* the action t was not executed, we leave it in the stack *)inletremaining=looplistin(* Utils.(printd debug_custom "Remaining size %i" (List.length remaining)); *)Var.updatestack(funmodified->insert_sublistmodifiedremaining);matchVar.getstackwith|[]->-1(* wait forever until next event *)|hd::_->(* ensure returned value is never negative,
since that would mean wait forever *)maxTime.(hd.timeout-Time.now())0letrun()=(* the stack should be empty most of the time, so we add a test to be faster *)ifVar.getstack<>[]theniterstackelse-1(* wait forever until next event *)