123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691open!Coreopen!ImportopenBonsai.For_openopenBonsai.Let_syntaxmoduleQuery_response_tracker=Bonsai.Effect.For_testing.Query_response_trackeropenProcletsexp_of_packed_computation:typea.aBonsai.Private.Computation.t->Sexp.t=funt->Bonsai.Private.Skeleton.Computation.of_computationt|>Bonsai.Private.Skeleton.Computation.sanitize_for_testing|>Bonsai.Private.Skeleton.Computation.minimal_sexp_of_t;;let%expect_test"cutoff"=letvar=Bonsai.Var.create0inletvalue=Bonsai.Var.valuevarinletcomponent=return@@Value.cutoffvalue~equal:(funab->a%2=b%2)inlethandle=Handle.create(Result_spec.string(moduleInt))componentinHandle.showhandle;[%expect{| 0 |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{| 0 |}];Bonsai.Var.setvar1;Handle.showhandle;[%expect{| 1 |}];;let%expect_test"debug on change"=letvar=Bonsai.Var.create0inletvalue=Bonsai.Var.valuevarinletcomponent=Bonsai.Debug.on_changevalue~f:(funi->printf"%d"i)inlethandle=Handle.createResult_spec.invisiblecomponentinHandle.showhandle;[%expect{| 0 |}];Bonsai.Var.setvar1;Handle.showhandle;[%expect{| 1 |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{| 2 |}];;let%expect_test"Setting cutoff on Bonsai values should not change previously set cutoffs"=letvar=Bonsai.Var.create(0,0)inletvalue=Bonsai.Var.valuevarinletcomponent=let%subpair=return(Value.cutoff~equal:phys_equalvalue)inlet%sub_=return(Value.cutoffvalue~equal:(fun(a1,_)(a2,_)->a1=a2))inreturnpairinlethandle=Handle.create(Result_spec.sexp(modulestructtypet=int*int[@@derivingsexp]end))componentinHandle.showhandle;[%expect{| (0 0) |}];Bonsai.Var.setvar(1,0);Handle.showhandle;[%expect{| (1 0) |}];Bonsai.Var.setvar(1,2);Handle.showhandle;[%expect{| (1 2) |}];;let%expect_test"Cutoff set by let%arr ppx should not be applied to different \
incremental nodes"=letvar=Bonsai.Var.create(0,0)inletvalue=Bonsai.Var.valuevarinletcomponent=let%subpair=returnvalueinlet%sub_=let%arra,_=valueinainreturnpairinlethandle=Handle.create(Result_spec.sexp(modulestructtypet=int*int[@@derivingsexp]end))componentinHandle.showhandle;[%expect{| (0 0) |}];Bonsai.Var.setvar(1,0);Handle.showhandle;[%expect{| (1 0) |}];Bonsai.Var.setvar(1,2);Handle.showhandle;[%expect{| (1 2) |}];;let%expect_test"Cutoff propragates on named values regression"=(* This test tests against a regression on [cutoff].
Since named values are evaled into a map, and
[Value.cutoff] compiled to the mutable [Incremental.set_cutoff], everytime
that set_cutoff happens it affects all occurrences of the named value.
This is tested for here by giving the same named value node different
cutoff functions (one for the left element and another for the second element)
and showcasing that each node is not affected by the other cutoff node.
*)letvar=Bonsai.Var.create(0,0)inletvalue=Bonsai.Var.valuevarinletcomponent=let%subtupled_input=returnvalueinlet%subleft=lettupled_input=Value.cutofftupled_input~equal:(fun(old,_)(new_,_)->phys_equaloldnew_)inlet%arrleft,_=tupled_inputinleftinlet%subright=lettupled_input=Value.cutofftupled_input~equal:(fun(_,old)(_,new_)->phys_equaloldnew_)inlet%arr_,right=tupled_inputinrightinlet%arrleft=leftandright=rightinleft,rightinlethandle=Handle.create(Result_spec.sexp(modulestructtypet=int*int[@@derivingsexp]end))componentinHandle.showhandle;[%expect{| (0 0) |}];(* First element changes. *)Bonsai.Var.setvar(1,0);(* Missed trigger! *)Handle.showhandle;[%expect{| (1 0) |}];Bonsai.Var.setvar(1,2);(* When the second element changes, this is fine since its cutoff function
won.*)Handle.showhandle;[%expect{| (1 2) |}];;let%expect_test"What happens when cutoff nodes are nested?"=letvar=Bonsai.Var.create(0,0)inletvalue=Bonsai.Var.valuevarinletcomponent=letfirst_cutoff=Value.cutoffvalue~equal:(fun(_,a)(_,b)->phys_equalab)inletsecond_cutoff=Value.cutofffirst_cutoff~equal:(fun(a,_)(b,_)->phys_equalab)inreturnsecond_cutoffinlethandle=Handle.create(Result_spec.sexp(modulestructtypet=int*int[@@derivingsexp]end))componentinHandle.showhandle;[%expect{| (0 0) |}];(* First element changes. *)Bonsai.Var.setvar(1,0);Handle.showhandle;(* Does not recompute! (first cutoff still says they're equal.) *)[%expect{| (0 0) |}];(* Second element changes. *)Bonsai.Var.setvar(0,2);(* Does not recompute! (second cutoff still says they're equal.) *)Handle.showhandle;[%expect{| (0 0) |}];Bonsai.Var.setvar(1,2);(* Only once both cutoffs say that they're unequal, recomputation happens. *)Handle.showhandle;[%expect{| (1 2) |}];;let%expect_test"arrow-syntax"=letcomponent=let%suba=Bonsai.const"hi"inlet%subb=Bonsai.const5inlet%arra=aandb=binsprintf"%s %d"abinlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| hi 5 |}];;let%expect_test"mapn"=let(_:unitComputation.t)=let%mapn.Computation()=Bonsai.const()and()=Bonsai.const()and()=Bonsai.const()in()in();;let%expect_test"if%sub"=letcomponentinput=leta=Value.return"hello"inletb=Value.return"world"inif%subinputthenBonsai.readaelseBonsai.readbinletvar=Bonsai.Var.createtrueinlethandle=Handle.create(Result_spec.string(moduleString))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{| hello |}];Bonsai.Var.setvarfalse;Handle.showhandle;[%expect{| world |}];;let%expect_test"call component"=letadd_one=Bonsai.pure(funx->x+1)inletcomponentinput=let%suba=add_oneinputinreturnainletvar=Bonsai.Var.create1inlethandle=Handle.create(Result_spec.sexp(moduleInt))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{| 2 |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{| 3 |}];;let%expect_test"store named in a ref"=letname_ref=refNoneinletcomponent=let%subx=let%suba=opaque_const5inname_ref:=Somea;let%arra=ainainlet%arrx=xandy=Option.value_exn!name_refinx+yinExpect_test_helpers_core.require_does_raise[%here](fun()->Handle.create(Result_spec.sexp(moduleInt))component);[%expect{| "A Value.t introduced by the [let%sub] expression at TEST_FILENAME:0:0 was used outside of the scope that it was declared in. Make sure that you aren't storing it inside a ref." |}];;let%expect_test"on_display"=letcomponent=let%substate,set_state=Bonsai.state(moduleInt)~default_model:0inletupdate=let%mapstate=stateandset_state=set_stateinset_state(state+1)inlet%sub()=Bonsai.Edge.after_displayupdateinreturnstateinlethandle=Handle.create(Result_spec.sexp(moduleInt))componentinHandle.showhandle;[%expect{| 0 |}];Handle.showhandle;[%expect{| 1 |}];Handle.showhandle;[%expect{| 2 |}];Handle.showhandle;[%expect{| 3 |}];;let%expect_test"on_display for updating a state"=letcomponentinput=let%substate,set_state=Bonsai.state_opt(moduleInt)inlet%subupdate=match%substatewith|None->return@@let%mapset_state=set_stateandinput=inputinSome(set_state(Someinput))|Somestate->return@@let%mapstate=stateandset_state=set_stateandinput=inputinifInt.equalstateinputthenNoneelseSome(set_state(Someinput))inlet%sub()=Bonsai.Edge.after_display'updateinreturn(Value.bothinputstate)inletvar=Bonsai.Var.create1inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=int*intoption[@@derivingsexp_of]end))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{| (1 ()) |}];Handle.showhandle;[%expect{| (1 (1)) |}];Handle.showhandle;[%expect{| (1 (1)) |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{| (2 (1)) |}];Handle.showhandle;[%expect{| (2 (2)) |}];Handle.showhandle;[%expect{| (2 (2)) |}];;let%expect_test"path"=letcomponent=let%sub()=opaque_const()inlet%subpath=Bonsai.Private.pathinreturn(Value.mappath~f:Bonsai.Private.Path.sexp_of_t)inlethandle=Handle.create(Result_spec.sexp(moduleSexp))componentinHandle.showhandle;(* The first of these "Subst_from" is actually a component that is
added by the testing helpers. *)[%expect{| (Subst_from Subst_into Subst_into Subst_from) |}];;let%expect_test"assoc and enum path"=letcomponent=Bonsai.assoc(moduleInt)(opaque_const_value(Int.Map.of_alist_exn[-1,();1,()]))~f:(funi_->if%subi>>|(>)0thenBonsai.Private.pathelseBonsai.Private.path)inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=Bonsai.Private.Path.tInt.Map.t[@@derivingsexp_of]end))componentinHandle.showhandle;[%expect{|
((-1 (Subst_from (Assoc -1) Subst_into (Switch 0)))
(1 (Subst_from (Assoc 1) Subst_into (Switch 1)))) |}];;let%expect_test"constant folded assoc path"=letcomponent=Bonsai.assoc(moduleInt)(Value.return(Int.Map.of_alist_exn[-1,();1,()]))~f:(fun__->(* NOTE: Since this test case uses both a constant map and previously
only made use of the path, then this combination resulted in the optimization
that makes a call to Map.mapi directly to trigger. To avoid this, we
artifically introduce some state, and more importantly, use the state trivially
such that the simplication optimization is not triggered. *)let%subx,_=Bonsai.state(moduleInt)~default_model:0inlet%subpath=Bonsai.Private.pathinlet%subpath,_=let%arrpath=pathandx=xinpath,xinBonsai.readpath)inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=Bonsai.Private.Path.tInt.Map.t[@@derivingsexp_of]end))componentinHandle.showhandle;[%expect{|
((-1
(Subst_from Subst_from Subst_from Subst_from Subst_into Subst_into
Subst_from))
(1
(Subst_from Subst_from Subst_into Subst_from Subst_from Subst_into
Subst_into Subst_from))) |}];;let%expect_test"constant folded assoc lifecycles are unchanged"=letruntestinput=letcomponent=let%sub_=Bonsai.assoc(moduleInt)(input(Int.Map.of_alist_exn[-1,();1,()]))~f:(funkey_->Bonsai.Edge.lifecycle~on_activate:(let%mapkey=keyinEffect.print_s[%message(key:int)])())inBonsai.const()inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.showhandleinruntestopaque_const_value;letunoptimized=Expect_test_helpers_base.expect_test_output[%here]inruntestValue.return;letoptimized=Expect_test_helpers_base.expect_test_output[%here]inprint_endline(Expect_test_patdiff.patdiff~context:0unoptimizedoptimized);;let%expect_test"constant map + simplifiable assoc ~f => constant map proper evaluation"=(* This test case just tests that the map function is applied properly. *)letcomponent=Bonsai.assoc(moduleString)(Value.return(String.Map.of_alist_exn["hello",0;"world",5]))~f:(fun_v->let%arrv=vinv+100)inletmoduleModel=structtypet=intString.Map.t[@@derivingsexp,equal]endinlethandle=Handle.create(Result_spec.sexp(moduleModel))componentinHandle.showhandle;[%expect{| ((hello 100) (world 105)) |}];;let%expect_test"assoc_on"=letvar=Bonsai.Var.create(Int.Map.of_alist_exn[0,();1,();2,()])inletcomponent=Bonsai.Expert.assoc_on(moduleInt)(moduleInt)(Bonsai.Var.valuevar)~get_model_key:(funkey_data->key%2)~f:(fun_key_data->Bonsai.state_machine1(moduleInt)(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:_inputmodelnew_model->matchinputwith|Active()->new_model|Inactive->print_endline"inactive";model)(opaque_const_value()))inlethandle=Handle.create(modulestructtypet=(int*(int->unitEffect.t))Int.Map.ttypeincoming=Nothing.tletincoming_=Nothing.unreachable_codeletview(map:t)=map|>Map.to_alist|>List.map~f:(fun(i,(s,_))->i,s)|>[%sexp_of:(int*int)list]|>Sexp.to_string_hum;;end)componentinHandle.showhandle;[%expect{| ((0 0) (1 0) (2 0)) |}];letresult=Handle.resulthandleinletset_twowhat=let_,set=Map.find_exnresult2inUi_effect.Expert.handle(setwhat)inset_two3;Handle.showhandle;[%expect{| ((0 3) (1 0) (2 3)) |}];Bonsai.Var.setvar(Int.Map.of_alist_exn[1,()]);Handle.showhandle;[%expect{| ((1 0)) |}];set_two4;Handle.showhandle;[%expect{|
inactive
((1 0)) |}];Bonsai.Var.setvar(Int.Map.of_alist_exn[1,();2,()]);Handle.showhandle;[%expect{| ((1 0) (2 3)) |}];;let%expect_test"simplify assoc_on"=letvar=Bonsai.Var.create(Int.Map.of_alist_exn[0,();1,();2,()])inletcomponent=Bonsai.Expert.assoc_on(moduleInt)(moduleInt)(Bonsai.Var.valuevar)~get_model_key:(funkey_data->key%2)~f:(fun_keydata->returndata)incomponent|>Bonsai.Private.reveal_computation|>Bonsai.Private.pre_process|>sexp_of_packed_computation|>print_s;[%expect{| (Assoc_simpl (map Incr)) |}];;let%expect_test"simple-assoc works with paths"=letcomponent=Bonsai.assoc(moduleString)(opaque_const_value(String.Map.of_alist_exn["hello",();"world",()]))~f:(fun__->let%suba=Bonsai.Private.pathinlet%subb=Bonsai.Private.pathinreturn(Bonsai.Value.bothab))inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=(Bonsai.Private.Path.t*Bonsai.Private.Path.t)String.Map.t[@@derivingsexp_of]end))componentinHandle.showhandle;[%expect{|
((hello
((Subst_from (Assoc hello) Subst_from)
(Subst_from (Assoc hello) Subst_into Subst_from)))
(world
((Subst_from (Assoc world) Subst_from)
(Subst_from (Assoc world) Subst_into Subst_from)))) |}];component|>Bonsai.Private.reveal_computation|>Bonsai.Private.pre_process|>sexp_of_packed_computation|>print_s;[%expect{|
(Assoc_simpl (map Incr)) |}];;lettest_assoc_simpl_on_cutoff~added_by_let_syntax=letcutoffvalue~equal=Bonsai.Private.reveal_valuevalue|>Bonsai.Private.Value.cutoff~added_by_let_syntax~equal|>Bonsai.Private.conceal_valueinletcomponent=Bonsai.assoc(moduleString)(opaque_const_value(String.Map.of_alist_exn["capy",();"bara",()]))~f:(fun_data->return(cutoff~equal:(fun__->true)data))inprint_sBonsai.Private.(sexp_of_packed_computation(Bonsai.Private.Pre_process.pre_process(reveal_computationcomponent)));;let%expect_test"assoc simplification behavior on cutoffs"=test_assoc_simpl_on_cutoff~added_by_let_syntax:true;[%expect{|
(Assoc_simpl (map Incr)) |}];test_assoc_simpl_on_cutoff~added_by_let_syntax:false;[%expect{|
(Assoc
(map Incr)
(key_id 1)
(cmp_id 2)
(data_id 3)
(by (
Return (value (Cutoff (t (Named (uid 3))) (added_by_let_syntax false)))))) |}];;let%expect_test"assoc_list unique"=letruninput=letcomponent=Bonsai.assoc_list(moduleInt)(opaque_const_valueinput)~get_key:fst~f:(fun_kv_pair->let%sub_,value=returnkv_pairinreturnvalue)inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=[`Okofstringlist|`Duplicate_keyofint][@@derivingsexp_of]end))componentinHandle.showhandleinrun[2,"a";1,"b";3,"c";1,"d"];[%expect{| (Duplicate_key 1) |}];run[2,"a";1,"c";3,"b"];[%expect{| (Ok (a c b)) |}];;let%expect_test"chain"=letadd_one=Bonsai.pure(funx->x+1)inletdouble=Bonsai.pure(funx->x*2)inletcomponentinput=let%suba=add_oneinputinlet%subb=doubleainreturnbinletvar=Bonsai.Var.create1inlethandle=Handle.create(Result_spec.sexp(moduleInt))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{| 4 |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{| 6 |}];;let%expect_test"chain + both"=letadd_one=Bonsai.pure(funx->x+1)inletdouble=Bonsai.pure(funx->x*2)inletadd=Bonsai.pure(fun(x,y)->x+y)inletcomponentinput=let%suba=add_oneinputinlet%subb=doubleainlet%subc=add(Value.bothab)inreturncinletvar=Bonsai.Var.create1inlethandle=Handle.create(Result_spec.sexp(moduleInt))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{| 6 |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{| 9 |}];;let%expect_test"wrap"=letcomponent=Bonsai.wrap(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:_(result,_)model()->String.lengthresult+model)~f:(funmodelinject->return@@let%mapmodel=modelandinject=injectinInt.to_stringmodel,inject)inlethandle=Handle.create(modulestructtypet=string*(unit->unitEffect.t)typeincoming=unitletview=Tuple2.get1letincoming(_,x)()=x()end)componentinHandle.showhandle;[%expect{| 0 |}];Handle.do_actionshandle[()];Handle.showhandle;[%expect{| 1 |}];Handle.do_actionshandle[();();();();();();();();();()];Handle.showhandle;[%expect{| 12 |}];Handle.do_actionshandle[()];Handle.showhandle;[%expect{| 14 |}];;let%expect_test"match%sub"=letvar:(string,int)Either.tBonsai.Var.t=Bonsai.Var.create(Either.First"hello")inletcomponent=match%subBonsai.Var.valuevarwith|Firsts->Bonsai.read(Value.maps~f:(sprintf"%s world"))|Secondi->Bonsai.read(Value.mapi~f:Int.to_string)inlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| hello world |}];Bonsai.Var.setvar(Second2);Handle.showhandle;[%expect{| 2 |}];;let%expect_test"match%sub"=letvar:(string,int)Either.tBonsai.Var.t=Bonsai.Var.create(Either.First"hello")inletcomponent=match%subBonsai.Var.valuevarwith|Firsts->Bonsai.read(Value.maps~f:(sprintf"%s world"))|Secondi->Bonsai.read(Value.mapi~f:Int.to_string)inlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| hello world |}];Bonsai.Var.setvar(Second2);Handle.showhandle;[%expect{| 2 |}];;typething=|Loadingofstring|Search_resultsofintlet%expect_test"match%sub repro"=letopenBonsai.Let_syntaxinletcomponentcurrent_page=match%subcurrent_pagewith|Loadingx->Bonsai.read(let%mapx=xin"loading "^x)|Search_resultss->Bonsai.read(let%maps=sinsprintf"search results %d"s)inletvar=Bonsai.Var.create(Loading"hello")inlethandle=Handle.create(Result_spec.string(moduleString))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{| loading hello |}];Bonsai.Var.setvar(Search_results5);Handle.showhandle;[%expect{| search results 5 |}];;let%expect_test"if%sub"=letcomponentinput=leta=Value.return"hello"inletb=Value.return"world"inif%subinputthenBonsai.readaelseBonsai.readbinletvar=Bonsai.Var.createtrueinlethandle=Handle.create(Result_spec.string(moduleString))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{| hello |}];Bonsai.Var.setvarfalse;Handle.showhandle;[%expect{| world |}];;let%expect_test"match%sub defers exceptions until runtime"=letvar=Bonsai.Var.createtrueinletcomponent=match%subBonsai.Var.valuevarwith|true->Bonsai.const"yay!"|false->assertfalseinlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| yay! |}];;let%expect_test"let%sub patterns"=letcomponent=let%suba,_b=Bonsai.const("hello world",5)inreturnainlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| hello world |}];;let%expect_test"sub constant folding optimization"=letcomponent=let%suba=Bonsai.const5inlet%subb=Bonsai.const6inreturn(let%mapa=aandb=bina+b)inprint_sBonsai.Private.(sexp_of_packed_computation(reveal_computationcomponent));[%expect{|
(Sub
(from (Return (value (Constant (id 0)))))
(via 1)
(into (
Sub
(from (Return (value (Constant (id 2)))))
(via 3)
(into (
Return (
value (
Mapn (
inputs ((
Mapn (
inputs (
(Named (uid 1))
(Named (uid 3)))))))))))))) |}];letcomponent=component|>Bonsai.Private.reveal_computation|>Bonsai.Private.Constant_fold.constant_fold|>Bonsai.Private.conceal_computationinprint_sBonsai.Private.(sexp_of_packed_computation(reveal_computationcomponent));[%expect{| (Return (value (Constant (id 0)))) |}];letcomponent=component|>Bonsai.Private.reveal_computation|>Bonsai.Private.conceal_computationinprint_sBonsai.Private.(sexp_of_packed_computation(reveal_computationcomponent));[%expect{| (Return (value (Constant (id 0)))) |}];;let%expect_test"let%map constant folding optimization"=letcomponent=let%suba=let%arra=Bonsai.Value.return5ina+1inlet%subb=Bonsai.const6inreturn(let%mapa=aandb=bina+b)inprint_sBonsai.Private.(sexp_of_packed_computation(reveal_computationcomponent));[%expect{|
(Sub
(from (Return (value (Mapn (inputs ((Constant (id 0))))))))
(via 2)
(into (
Sub
(from (Return (value (Constant (id 3)))))
(via 4)
(into (
Return (
value (
Mapn (
inputs ((
Mapn (
inputs (
(Named (uid 2))
(Named (uid 4)))))))))))))) |}];letcomponent=component|>Bonsai.Private.reveal_computation|>Bonsai.Private.Constant_fold.constant_fold|>Bonsai.Private.conceal_computationinprint_sBonsai.Private.(sexp_of_packed_computation(reveal_computationcomponent));[%expect{| (Return (value (Constant (id 0)))) |}];letcomponent=component|>Bonsai.Private.reveal_computation|>Bonsai.Private.conceal_computationinprint_sBonsai.Private.(sexp_of_packed_computation(reveal_computationcomponent));[%expect{| (Return (value (Constant (id 0)))) |}];;let%expect_test"assoc simplifies its inner computation, if possible"=letvalue=opaque_const_valueString.Map.emptyinletcomponent=Bonsai.assoc(moduleString)value~f:(funkeydata->Bonsai.read(Value.bothkeydata))inprint_sBonsai.Private.(sexp_of_packed_computation(pre_process(reveal_computationcomponent)));[%expect{|
(Assoc_simpl (map Incr)) |}];;let%expect_test"assoc with sub simplifies its inner computation, if possible"=letvalue=opaque_const_valueString.Map.emptyinletcomponent=Bonsai.assoc(moduleString)value~f:(funkeydata->let%subkey=Bonsai.readkeyinBonsai.read(Bonsai.Value.bothkeydata))inprint_sBonsai.Private.(sexp_of_packed_computation(pre_process(reveal_computationcomponent)));[%expect{|
(Assoc_simpl (map Incr)) |}];;let%expect_test"assoc with sub simplifies its inner computation, if possible"=letvalue=opaque_const_valueString.Map.emptyinletcomponent=Bonsai.assoc(moduleString)value~f:(funkeydata->let%subkey=Bonsai.readkeyinBonsai.read(Bonsai.Value.bothkeydata))inprint_sBonsai.Private.(sexp_of_packed_computation(pre_process(reveal_computationcomponent)));[%expect{|
(Assoc_simpl (map Incr)) |}];;let%expect_test"map > lazy"=letopenBonsai.Let_syntaxinletmoduleM=structtypet={label:string;children:tInt.Map.t}endinletrecf~t~depth=let%sub{M.label;children}=returntinlet%subchildren=Bonsai.assoc(moduleInt)children~f:(fun_v->letdepth=let%mapdepth=depthindepth+1inBonsai.lazy_(lazy(f~t:v~depth)))inreturn@@let%maplabel=labelandchildren=childrenanddepth=depthin[%messagelabel(depth:int)(children:Sexp.tInt.Map.t)]inlett_var=Bonsai.Var.create{M.label="hi";children=Int.Map.empty}inlett_value=Bonsai.Var.valuet_varinlethandle=Handle.create(Result_spec.sexp(moduleSexp))(f~t:t_value~depth:(Bonsai.Value.return0))in[%expect{| |}];Handle.showhandle;[%expect{| (hi (depth 0) (children ())) |}];Bonsai.Var.sett_var{M.label="hi";children=Int.Map.singleton0{M.label="hello";children=Int.Map.empty}};Handle.showhandle;[%expect{| (hi (depth 0) (children ((0 (hello (depth 1) (children ())))))) |}];;let%expect_test"dynamic action sent to non-existent assoc element"=letvar=Bonsai.Var.create(Int.Map.of_alist_exn[1,();2,()])inletcomponent=Bonsai.assoc(moduleInt)(Bonsai.Var.valuevar)~f:(fun_key_data->Bonsai.state_machine1(moduleInt)(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:_inputmodelnew_model->matchinputwith|Active()->new_model|Inactive->print_endline"inactive";model)(opaque_const_value()))inlethandle=Handle.create(modulestructtypet=(int*(int->unitEffect.t))Int.Map.ttypeincoming=Nothing.tletincoming_=Nothing.unreachable_codeletview(map:t)=map|>Map.to_alist|>List.map~f:(fun(i,(s,_))->i,s)|>[%sexp_of:(int*int)list]|>Sexp.to_string_hum;;end)componentinHandle.showhandle;[%expect{| ((1 0) (2 0)) |}];letresult=Handle.resulthandleinletset_twowhat=let_,set=Map.find_exnresult2inUi_effect.Expert.handle(setwhat)inset_two3;Handle.showhandle;[%expect{| ((1 0) (2 3)) |}];Bonsai.Var.setvar(Int.Map.of_alist_exn[1,()]);Handle.showhandle;[%expect{| ((1 0)) |}];set_two4;Handle.showhandle;[%expect{|
inactive
((1 0)) |}];Bonsai.Var.setvar(Int.Map.of_alist_exn[1,();2,()]);Handle.showhandle;[%expect{| ((1 0) (2 3)) |}];;let%expect_test"ping-pong between apply-static and dynamic"=letcomponent=let%subm,i,_=Bonsai.Expert.state_machine01(moduleInt)(moduleUnit)(moduleUnit)~default_model:9~apply_dynamic:(fun~inject_dynamic:_~inject_static~schedule_event()model()->ifmodel<=0thenmodelelse(printf"%d: Ping!\n"model;schedule_event(inject_static());model-1))~apply_static:(fun~inject_dynamic~inject_static:_~schedule_eventmodel()->printf"%d: Pong!\n"model;schedule_event(inject_dynamic());model-1)(Value.return())inlet%arrm=mandi=iinm,iinlethandle=Handle.create(modulestructtypet=int*(unit->unitEffect.t)typeincoming=unitletincoming(_,i)=iletview(x,_)=Int.to_stringxend)componentinHandle.do_actionshandle[()];Handle.flushhandle;[%expect{|
9: Ping!
8: Pong!
7: Ping!
6: Pong!
5: Ping!
4: Pong!
3: Ping!
2: Pong!
1: Ping!
0: Pong! |}];;let%test_module"inactive delivery"=(modulestructletreccensor_sexp=function|Sexp.Listl->(matchList.filter_mapl~f:censor_sexpwith|[]->None|[x]->Somex|all->Some(Sexp.Listall))|Sexp.Atoms->ifString.is_prefixs~prefix:"lib/bonsai"thenNoneelseSome(Atoms);;letprint_computationcomputation=computation(Bonsai.Value.return())|>Bonsai.Private.reveal_computation|>Bonsai.Private.pre_process|>sexp_of_packed_computation|>censor_sexp|>Option.value~default:(Sexp.List[])|>print_s;;lettest_delivery_to_inactive_componentcomputation=letrun_testwhich_assoc=letvar=Bonsai.Var.create(Int.Map.of_alist_exn[1,();2,()])inletcomponent=matchwhich_assocwith|`Assoc->let%subi=Bonsai.const()inBonsai.assoc(moduleInt)(Bonsai.Var.valuevar)~f:(fun_key_data->computationi)|`Assoc_on->let%subi=Bonsai.const()inBonsai.Expert.assoc_on(moduleInt)(moduleString)(Bonsai.Var.valuevar)~get_model_key:(funkey_data->Int.to_stringkey)~f:(fun_key_data->computationi)inlethandle=Handle.create(modulestructtypet=(int*(int->unitEffect.t))Int.Map.ttypeincoming=Nothing.tletincoming_=Nothing.unreachable_codeletview(map:t)=map|>Map.to_alist|>List.map~f:(fun(i,(s,_))->i,s)|>[%sexp_of:(int*int)list]|>Sexp.to_string_hum;;end)componentinHandle.showhandle;letresult=Handle.resulthandleinletset_twowhat=let_,set=Map.find_exnresult2inUi_effect.Expert.handle(setwhat)inset_two3;Handle.showhandle;Bonsai.Var.setvar(Int.Map.of_alist_exn[1,()]);Handle.showhandle;set_two4;Handle.showhandle;Bonsai.Var.setvar(Int.Map.of_alist_exn[1,();2,()]);Handle.showhandle;Expect_test_helpers_base.expect_test_output[%here]inletassoc=run_test`Associnletassoc_on=run_test`Assoc_oninprint_computationcomputation;print_endlineassoc;print_endline"==== Diff between assoc and assoc_on: ====";print_endline(Expect_test_patdiff.patdiff~context:0assocassoc_on);;let%expect_test"state_machine1 inactive-delivery"=(fun_->Bonsai.state_machine1(moduleInt)(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:_input_modelnew_model->(matchinputwith|Inactive->print_endline"static action"|Active()->print_endline"dynamic action");new_model)(Value.return()))|>test_delivery_to_inactive_component;[%expect{|
Leaf0
((1 0) (2 0))
dynamic action
((1 0) (2 3))
((1 0))
dynamic action
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"race inactive-delivery (but an active input)"=(funinput->Bonsai.state_machine1(moduleInt)(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:_input_modelnew_model->(matchinputwith|Inactive->print_endline"static action"|Active()->print_endline"dynamic action");new_model)input)|>test_delivery_to_inactive_component;[%expect{|
Leaf0
((1 0) (2 0))
dynamic action
((1 0) (2 3))
((1 0))
dynamic action
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"dynamic action inactive-delivery"=(fun_->Bonsai.state_machine1(moduleInt)(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:_inputmodelnew_model->matchinputwith|Active()->new_model|Inactive->print_endline"inactive";model)(opaque_const_value()))|>test_delivery_to_inactive_component;[%expect{|
(Leaf1 (input Incr))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
inactive
((1 0))
((1 0) (2 3))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"actor1 inactive-delivery"=(fun_->Bonsai.actor1(moduleInt)(moduleInt)~default_model:0~recv:(fun~schedule_event:_inputmodelnew_model->matchinputwith|Active()->new_model,()|Inactive->print_endline"action sent to actor1 has been received while the input was inactive.";model,())(opaque_const_value()))|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from (Leaf1 (input Incr)))
(via 4)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 4)))))))
(via 6)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 4)))))))
(via 8)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 8)))))))
(via 10)
(into (
Return (
value (
Mapn (
inputs (
(Named (uid 6))
(Named (uid 10)))))))))))))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
action sent to actor1 has been received while the input was inactive.
((1 0))
((1 0) (2 3))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"actor0 inactive-delivery"=(fun_->Bonsai.actor0(moduleInt)(moduleInt)~default_model:0~recv:(fun~schedule_event:__modelnew_model->new_model,()))|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from Leaf0)
(via 0)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 0)))))))
(via 2)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 0)))))))
(via 4)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 4)))))))
(via 6)
(into (
Return (
value (
Mapn (
inputs (
(Named (uid 2))
(Named (uid 6)))))))))))))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"actor1 with constant input downgrades to actor0"=(fun_->Bonsai.actor1(moduleInt)(moduleInt)~default_model:0~recv:(fun~schedule_event:_inputmodelnew_model->matchinputwith|Active()->new_model,()|Inactive->print_endline"action sent to actor1 has been received while the input was inactive.";model,())(Value.return()))|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from Leaf0)
(via 0)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 0)))))))
(via 2)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 0)))))))
(via 4)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 4)))))))
(via 6)
(into (
Return (
value (
Mapn (
inputs (
(Named (uid 2))
(Named (uid 6)))))))))))))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"dynamic action stat_machine01 inactive-delivery"=(fun_->let%subm,i,_=Bonsai.Expert.state_machine01(moduleInt)(moduleInt)(moduleNothing)~default_model:0~apply_dynamic:(fun~inject_dynamic:_~inject_static:_~schedule_event:_()_modelnew_model->new_model)~apply_static:(fun~inject_dynamic:_~inject_static:_~schedule_event:__model->Nothing.unreachable_code)(Value.return())inlet%arrm=mandi=iinm,i)|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from (Leaf01 (input (Constant (id 0)))))
(via 5)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 5)))))))
(via 7)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 5)))))))
(via 9)
(into (
Return (
value (
Mapn (
inputs (
(Named (uid 7))
(Named (uid 9)))))))))))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
("An action sent to a [state_machine01] has been dropped because its input was not present. This happens when the [state_machine01] is inactive when it receives a message."
(action 4))
((1 0))
((1 0) (2 3))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"static action stat_machine01 inactive-delivery"=(fun_->let%subm,_,i=Bonsai.Expert.state_machine01(moduleInt)(moduleNothing)(moduleInt)~default_model:0~apply_dynamic:(fun~inject_dynamic:_~inject_static:_~schedule_event:_()_model->Nothing.unreachable_code)~apply_static:(fun~inject_dynamic:_~inject_static:_~schedule_event:__modelnew_model->new_model)(Value.return())inlet%arrm=mandi=iinm,i)|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from (Leaf01 (input (Constant (id 0)))))
(via 5)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 5)))))))
(via 7)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 5)))))))
(via 9)
(into (
Return (
value (
Mapn (
inputs (
(Named (uid 7))
(Named (uid 9)))))))))))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"static action inactive-delivery"=(fun_->Bonsai.state(moduleInt)~default_model:0)|>test_delivery_to_inactive_component;[%expect{|
Leaf0
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"static inside of a lazy"=(fun_->opaque_computation(Bonsai.lazy_(lazy(Bonsai.state(moduleInt)~default_model:0))))|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from (Return (value Incr)))
(via 1)
(into (
Switch
(match_ (Mapn (inputs (Named (uid 1)))))
(arms ((Lazy t) (Return (value Exception)))))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"static inside of a lazy (optimized away)"=(fun_->Bonsai.lazy_(lazy(Bonsai.state(moduleInt)~default_model:0)))|>test_delivery_to_inactive_component;[%expect{|
Leaf0
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"static inside of a wrap"=(fun_->Bonsai.wrap(moduleUnit)~default_model:()~apply_action:(fun~inject:_~schedule_event:__()()->())~f:(fun_model_inject->Bonsai.state(moduleInt)~default_model:0))|>test_delivery_to_inactive_component;[%expect{|
(Wrap
(model_id 4)
(inject_id 0)
(inner Leaf0))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"static inside of a match%sub"=(fun_->match%subopaque_const_value()with|()->Bonsai.state(moduleInt)~default_model:0)|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from (Return (value Incr)))
(via 1)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 1)))))))
(via 3)
(into Leaf0))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"static inside of a with_model_resetter"=(fun_->let%subr,_reset=Bonsai.with_model_resetter(Bonsai.state(moduleInt)~default_model:0)inreturnr)|>test_delivery_to_inactive_component;[%expect{|
(Sub
(from (
With_model_resetter
(inner (
Sub
(from Leaf0)
(via 1)
(into (
Return (
value (
Mapn (
inputs (
(Named (uid 1))
(Named (uid 0))))))))))
(reset_id 0)))
(via 3)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 3)))))))
(via 5)
(into (
Sub
(from (Return (value (Mapn (inputs (Named (uid 3)))))))
(via 7)
(into (Return (value (Named (uid 5))))))))))
((1 0) (2 0))
((1 0) (2 3))
((1 0))
((1 0))
((1 0) (2 4))
==== Diff between assoc and assoc_on: ==== |}];;let%expect_test"resetting while inactive"=letwhich_branch=Bonsai.Var.createtrueinletcomponent=if%subBonsai.Var.valuewhich_branchthenBonsai.with_model_resetter(Bonsai.state(moduleInt)~default_model:0)elseBonsai.const((-1,fun_->Effect.Ignore),Effect.Ignore)inlethandle=Handle.create(modulestructtypet=(int*(int->unitEffect.t))*unitEffect.ttypeincoming=Nothing.tletincoming_=Nothing.unreachable_codeletview((i,_),_)=Int.to_stringiend)componentinHandle.showhandle;let(_,set_value),reset=Handle.resulthandleinletset_valuei=Ui_effect.Expert.handle(set_valuei)inletreset()=Ui_effect.Expert.handleresetinset_value3;Handle.showhandle;Bonsai.Var.setwhich_branchfalse;Handle.showhandle;set_value4;Handle.showhandle;Bonsai.Var.setwhich_branchtrue;Handle.showhandle;[%expect{|
0
3
-1
-1
4 |}];Bonsai.Var.setwhich_branchfalse;Handle.showhandle;[%expect{| -1 |}];reset();Bonsai.Var.setwhich_branchtrue;Handle.showhandle;[%expect{| 0 |}];;let%expect_test"resetting while inactive via the reset passed in"=letwhich_branch=Bonsai.Var.createtrueinletcomponent=if%subBonsai.Var.valuewhich_branchthenBonsai.with_model_resetter'(fun~reset->Bonsai.Computation.both(Bonsai.state(moduleInt)~default_model:0)(returnreset))elseBonsai.const((-1,fun_->Effect.Ignore),Effect.Ignore)inlethandle=Handle.create(modulestructtypet=(int*(int->unitEffect.t))*unitEffect.ttypeincoming=Nothing.tletincoming_=Nothing.unreachable_codeletview((i,_),_)=Int.to_stringiend)componentinHandle.showhandle;let(_,set_value),reset=Handle.resulthandleinletset_valuei=Ui_effect.Expert.handle(set_valuei)inletreset()=Ui_effect.Expert.handleresetinset_value3;Handle.showhandle;Bonsai.Var.setwhich_branchfalse;Handle.showhandle;set_value4;Handle.showhandle;Bonsai.Var.setwhich_branchtrue;Handle.showhandle;[%expect{|
0
3
-1
-1
4 |}];Bonsai.Var.setwhich_branchfalse;Handle.showhandle;[%expect{| -1 |}];reset();Bonsai.Var.setwhich_branchtrue;Handle.showhandle;[%expect{| 0 |}];;let%test_module"component reset"=(modulestructtype'aaction=|Actionof'a|Resetletbuild_handle(typeresultincoming)component~sexp_of=Handle.create(modulestructtypet=(result*(incoming->unitEffect.t))*unitEffect.ttypenonrecincoming=incomingactionletincoming((_result,do_action),reset)=function|Actionaction->do_actionaction|Reset->reset;;letview((result,_),_)=sexp_ofresult|>Sexp.to_string_humend)(Bonsai.with_model_resettercomponent);;let%expect_test"custom reset"=letcomponent=Bonsai.state(moduleInt)~default_model:0~reset:(funm->m*2)inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletset_valuei=Handle.do_actionshandle[Actioni]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];set_value3;Handle.showhandle;[%expect{| 3 |}];set_value4;Handle.showhandle;[%expect{| 4 |}];reset();Handle.showhandle;[%expect{| 8 |}];set_value1;reset();Handle.showhandle;[%expect{| 2 |}];set_value1;reset();set_value10;Handle.showhandle;[%expect{| 10 |}];;let%expect_test"reset by bouncing back to an action (state_machine0)"=letcomponent=Bonsai.state_machine0(moduleInt)(moduleBool)~default_model:0~apply_action:(fun~inject:_~schedule_event:_modelis_increment->ifis_incrementthenmodel+1else999)~reset:(fun~inject~schedule_eventmodel->schedule_event(injectfalse);model)inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletincrement()=Handle.do_actionshandle[Actiontrue]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];increment();increment();Handle.showhandle;[%expect{| 2 |}];increment();Handle.showhandle;[%expect{| 3 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"reset by bouncing back to an action (state_machine1)"=letcomponent=Bonsai.state_machine1(moduleInt)(moduleBool)(opaque_const_value())~default_model:0~apply_action:(fun~inject:_~schedule_event:_inputmodelis_increment->matchinputwith|Active()->ifis_incrementthenmodel+1else999|Inactive->print_endline"inactive";model)~reset:(fun~inject~schedule_eventmodel->schedule_event(injectfalse);model)inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletincrement()=Handle.do_actionshandle[Actiontrue]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];increment();Handle.showhandle;[%expect{| 1 |}];increment();Handle.showhandle;[%expect{| 2 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"inside match%sub"=letcomponent=match%subopaque_const_valuetruewith|true->Bonsai.state(moduleInt)~default_model:0~reset:(fun_->999)|false->assertfalseinlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletset_valuei=Handle.do_actionshandle[Actioni]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];set_value3;Handle.showhandle;[%expect{| 3 |}];set_value4;Handle.showhandle;[%expect{| 4 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"inside forced lazy"=letcomponent=match%subopaque_const_valuetruewith|true->Bonsai.lazy_(lazy(Bonsai.state(moduleInt)~default_model:0~reset:(fun_->999)))|false->assertfalseinlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletset_valuei=Handle.do_actionshandle[Actioni]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];set_value3;Handle.showhandle;[%expect{| 3 |}];set_value4;Handle.showhandle;[%expect{| 4 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"next to an inactive infinitely-recursive lazy"=letrecinfinitely_recursive_component()=Bonsai.lazy_(lazy(infinitely_recursive_component()))inletcomponent=match%subopaque_const_valuetruewith|true->Bonsai.state(moduleInt)~default_model:0~reset:(fun_->999)|false->infinitely_recursive_component()inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletset_valuei=Handle.do_actionshandle[Actioni]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];set_value3;Handle.showhandle;[%expect{| 3 |}];set_value4;Handle.showhandle;[%expect{| 4 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"inside assoc"=letcomponent=let%submap=Bonsai.assoc(moduleInt)(opaque_const_value(Int.Map.of_alist_exn[0,();1,()]))~f:(fun__->Bonsai.state(moduleInt)~default_model:0~reset:(fun_->print_endline"resetting";999))inlet%arrmap=mapinletres=Map.to_alistmap|>List.map~f:(fun(k,(v,_))->k,v)inletsetter(i,v)=(Map.find_exnmapi|>Tuple2.get2)vinres,setterinlethandle=build_handlecomponent~sexp_of:[%sexp_of:(int*int)list]inHandle.showhandle;[%expect{| ((0 0) (1 0)) |}];letset_valueiv=Handle.do_actionshandle[Action(i,v)]inletreset()=Handle.do_actionshandle[Reset]inset_value03;Handle.showhandle;[%expect{| ((0 3) (1 0)) |}];set_value15;Handle.showhandle;[%expect{| ((0 3) (1 5)) |}];reset();Handle.showhandle;[%expect{|
resetting
resetting
((0 999) (1 999)) |}];;let%expect_test"reset by bouncing back to an action (race)"=letcomponent=let%submodel,inject=Bonsai.state_machine1(moduleInt)(moduleBool)(opaque_const_value())~default_model:0~apply_action:(fun~inject:_~schedule_event:_(_:unitBonsai.Computation_status.t)modelis_increment->ifis_incrementthenmodel+1else999)~reset:(fun~inject~schedule_eventmodel->schedule_event(injectfalse);model)inreturn(Value.bothmodelinject)inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletincrement()=Handle.do_actionshandle[Actiontrue]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];increment();Handle.showhandle;[%expect{| 1 |}];increment();Handle.showhandle;[%expect{| 2 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"reset by bouncing back to an action (state_machine01 static)"=letcomponent=let%submodel,_,inject=Bonsai.Expert.state_machine01(moduleInt)(moduleUnit)(moduleBool)(opaque_const_value())~default_model:0~apply_static:(fun~inject_dynamic:_~inject_static:_~schedule_event:_modelis_increment->ifis_incrementthenmodel+1else999)~apply_dynamic:(fun~inject_dynamic:_~inject_static:_~schedule_event:_()_->assertfalse)~reset:(fun~inject_dynamic:_~inject_static~schedule_eventmodel->schedule_event(inject_staticfalse);model)inreturn(Value.bothmodelinject)inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletincrement()=Handle.do_actionshandle[Actiontrue]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];increment();Handle.showhandle;[%expect{| 1 |}];increment();Handle.showhandle;[%expect{| 2 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"reset by bouncing back to an action (state_machine01 dynamic)"=letcomponent=let%submodel,inject,_=Bonsai.Expert.state_machine01(moduleInt)(moduleBool)(moduleUnit)(opaque_const_value())~default_model:0~apply_dynamic:(fun~inject_dynamic:_~inject_static:_~schedule_event:_()modelis_increment->ifis_incrementthenmodel+1else999)~apply_static:(fun~inject_dynamic:_~inject_static:_~schedule_event:__->assertfalse)~reset:(fun~inject_dynamic~inject_static:_~schedule_eventmodel->schedule_event(inject_dynamicfalse);model)inreturn(Value.bothmodelinject)inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletincrement()=Handle.do_actionshandle[Actiontrue]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];increment();Handle.showhandle;[%expect{| 1 |}];increment();Handle.showhandle;[%expect{| 2 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"for wrap"=letcomponent=Bonsai.wrap(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:__modelis_increment->ifis_incrementthenmodel+1else999)~reset:(fun~inject~schedule_eventmodel->schedule_event(injectfalse);model)~f:(funmodelinject->return(Value.bothmodelinject))inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletincrement()=Handle.do_actionshandle[Actiontrue]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];increment();Handle.showhandle;[%expect{| 1 |}];increment();Handle.showhandle;[%expect{| 2 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"inside a wrap"=letcomponent=Bonsai.wrap(moduleUnit)~default_model:()~apply_action:(fun~inject:_~schedule_event:__()()->())~f:(fun__->Bonsai.state_machine0(moduleInt)(moduleBool)~default_model:0~apply_action:(fun~inject:_~schedule_event:_modelis_increment->ifis_incrementthenmodel+1else999)~reset:(fun~inject~schedule_eventmodel->schedule_event(injectfalse);model))inlethandle=build_handlecomponent~sexp_of:[%sexp_of:int]inletincrement()=Handle.do_actionshandle[Actiontrue]inletreset()=Handle.do_actionshandle[Reset]inHandle.showhandle;[%expect{| 0 |}];increment();Handle.showhandle;[%expect{| 1 |}];increment();Handle.showhandle;[%expect{| 2 |}];reset();Handle.showhandle;[%expect{| 999 |}];;let%expect_test"inside assoc_on"=letcomponent=let%submap=Bonsai.Expert.assoc_on(moduleInt)(moduleBool)(opaque_const_value(Int.Map.of_alist_exn[0,true;1,false;2,true]))~get_model_key:(fun_->Fn.id)~f:(fun__->Bonsai.state(moduleInt)~default_model:0~reset:(fun_->print_endline"resetting";999))inlet%arrmap=mapinletres=Map.to_alistmap|>List.map~f:(fun(k,(v,_))->k,v)inletsetter(i,v)=(Map.find_exnmapi|>Tuple2.get2)vinres,setterinlethandle=build_handlecomponent~sexp_of:[%sexp_of:(int*int)list]inHandle.showhandle;[%expect{| ((0 0) (1 0) (2 0)) |}];letset_valueiv=Handle.do_actionshandle[Action(i,v)]inletreset()=Handle.do_actionshandle[Reset]inset_value03;Handle.showhandle;[%expect{| ((0 3) (1 0) (2 3)) |}];set_value15;Handle.showhandle;[%expect{| ((0 3) (1 5) (2 3)) |}];reset();Handle.recompute_viewhandle;(* notice that there are two printings of 'resetting' because even though
there's three active components, there are only two models between them *)[%expect{|
resetting
resetting |}];Handle.showhandle;[%expect{| ((0 999) (1 999) (2 999)) |}];;end);;let%expect_test"inactive delivery to assoc_on with shared model keys"=letvar=Bonsai.Var.create(Int.Map.of_alist_exn[1,();2,()])inletcomponent=Bonsai.Expert.assoc_on(moduleInt)(moduleUnit)(Bonsai.Var.valuevar)~get_model_key:(fun_key_data->())~f:(fun_key_data->Bonsai.state_machine1(moduleInt)(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_event:_inputmodelnew_model->matchinputwith|Active()->new_model|Inactive->print_endline"inactive";model)(opaque_const_value()))inlethandle=Handle.create(modulestructtypet=(int*(int->unitEffect.t))Int.Map.ttypeincoming=Nothing.tletincoming_=Nothing.unreachable_codeletview(map:t)=map|>Map.to_alist|>List.map~f:(fun(i,(s,_))->i,s)|>[%sexp_of:(int*int)list]|>Sexp.to_string_hum;;end)componentinprint_computation(fun_->component);Handle.showhandle;letresult=Handle.resulthandleinletsetkeyto_what=let_,set=Map.find_exnresultkeyinUi_effect.Expert.handle(setto_what)inletset_one=set1inletset_two=set2in(* Delivery to existing key in input map works *)set_two3;Handle.showhandle;Bonsai.Var.setvar(Int.Map.of_alist_exn[1,()]);Handle.showhandle;(* 2 is no longer in the input map, so setting it should fail, even though its model
is still in the model map *)set_two4;Handle.showhandle;(* 1 is still in the input map, however, so it can be set *)set_one5;Handle.showhandle;(* Reintroducing 2 will have it share the model *)Bonsai.Var.setvar(Int.Map.of_alist_exn[1,();2,()]);Handle.showhandle;[%expect{|
(Assoc_on
(map Incr)
(io_key_id 1)
(model_key_id 3)
(model_cmp_id 4)
(data_id 5)
(by (Leaf1 (input Incr))))
((1 0) (2 0))
((1 3) (2 3))
((1 3))
inactive
((1 3))
((1 5))
((1 5) (2 5)) |}];;end);;let%test_module"testing Bonsai internals"=(modulestruct(* This module tests internal details of Bonsai, and the results are sensitive to
implementation changes. *)[@@@alert"-rampantly_nondeterministic"]let%expect_test"remove unused models in assoc"=letvar=Bonsai.Var.createInt.Map.emptyinletmoduleState_with_setter=structtypet={state:string;set_state:string->unitEffect.t}endinletmoduleAction=structtypet=Setofstringendinletcomponent=Bonsai.assoc(moduleInt)(Bonsai.Var.valuevar)~f:(fun_key_data->let%subv=Bonsai.state(moduleString)~default_model:"hello"inreturn@@let%mapstate,set_state=vin{State_with_setter.state;set_state})inlethandle=Handle.create(modulestructtypet=State_with_setter.tInt.Map.ttypeincoming=int*Action.tletincoming(map:t)(id,action)=lett=Map.find_exnmapidinmatch(action:Action.t)with|Setvalue->t.set_statevalue;;letview(map:t)=map|>Map.to_alist|>List.map~f:(fun(i,{state;set_state=_})->i,state)|>[%sexp_of:(int*string)list]|>Sexp.to_string_hum;;end)componentinHandle.show_modelhandle;[%expect{|
() |}];Bonsai.Var.setvar(Int.Map.of_alist_exn[1,();2,()]);Handle.show_modelhandle;[%expect{|
() |}];(* use the setter to re-establish the default *)Handle.do_actionshandle[1,Set"test"];Handle.show_modelhandle;[%expect{| ((1 test)) |}];Handle.do_actionshandle[1,Set"hello"];Handle.show_modelhandle;[%expect{|
() |}];;end);;let%expect_test"multiple maps respect cutoff"=letcomponentinput=input|>Value.map~f:(fun(_:int)->())|>Value.map~f:(fun()->print_endline"triggered")|>returninletvar=Bonsai.Var.create1inlethandle=Handle.create(Result_spec.sexp(moduleUnit))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{|
triggered
() |}];Bonsai.Var.setvar2;(* Cutoff happens on the unit, so "triggered" isn't printed *)Handle.showhandle;[%expect{| () |}];;let%expect_test"let syntax is collapsed upon eval"=letcomputation=let%arr()=opaque_const_value()and()=opaque_const_value()and()=opaque_const_value()and()=opaque_const_value()and()=opaque_const_value()and()=opaque_const_value()and()=opaque_const_value()in()inletpacked=letopenBonsai.Privateinletcomputation=reveal_computationcomputationinlet(T{model;input=_;apply_static=_;apply_dynamic=_;static_action;dynamic_action;run;reset=_})=computation|>pre_process|>gatherinletT=Bonsai.Private.Meta.Action.Type_id.same_witness_exnMeta.Action.nothingstatic_actioninletT=Bonsai.Private.Meta.Action.Type_id.same_witness_exnMeta.Action.nothingdynamic_actioninletsnapshot=run~environment:Environment.empty~path:Path.empty~clock:Ui_incr.clock~inject_dynamic:Nothing.unreachable_code~inject_static:Nothing.unreachable_code~model:(Ui_incr.returnmodel.default)inSnapshot.resultsnapshot|>Ui_incr.packinletfilename=Stdlib.Filename.temp_file"incr""out"inUi_incr.Packed.save_dot_to_filefilename[packed];letdot_contents=In_channel.read_allfilenameinrequire[%here]~if_false_then_print_s:(lazy[%message"No Map7 node found"])(String.is_substringdot_contents~substring:"Map7");;let%test_unit"constant prop doesn't happen"=(* Just make sure that this expression doesn't crash *)let(_:intComputation.t)=match%subValue.return(First1)with|Firstx->Bonsai.readx|Secondx->Bonsai.readxin();;let%expect_test"ignored result of assoc"=letvar=Bonsai.Var.create(Int.Map.of_alist_exn[1,();2,()])inletcomponent=let%sub_=Bonsai.assoc(moduleInt)(Bonsai.Var.valuevar)~f:(fun_keydata->(* this sub is here to make sure that bonsai doesn't
optimize the component into an "assoc_simple" *)let%sub_=Bonsai.const()inBonsai.readdata)inBonsai.const()inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.showhandle;[%expect{|
() |}];Bonsai.Var.setvar(Int.Map.of_alist_exn[]);Expect_test_helpers_core.require_does_not_raise[%here](fun()->Handle.showhandle);[%expect{| () |}];;let%expect_test"constant_folding on assoc containing a lifecycle"=letcomponent=Bonsai.assoc(moduleInt)(opaque_const_valueInt.Map.empty)~f:(fun_keydata->let%sub()=Bonsai.Edge.lifecycle()~on_activate:(Value.return(Ui_effect.print_s[%message"hello"]))inreturndata)incomponent|>Bonsai.Private.reveal_computation|>Bonsai.Private.pre_process|>sexp_of_packed_computation|>print_s;[%expect{|
(Assoc
(map Incr)
(key_id 1)
(cmp_id 2)
(data_id 3)
(by (
Sub
(from (Lifecycle (value (Constant (id 12)))))
(via 14)
(into (
Sub
(from (Return (value (Mapn (inputs ((Named (uid 14))))))))
(via 16)
(into (Return (value (Named (uid 3)))))))))) |}];;let%expect_test"constant_folding on assoc containing a lifecycle that depends on a \
value bound outside"=letcomponent=let%suba=opaque_const"hello"inBonsai.assoc(moduleInt)(opaque_const_valueInt.Map.empty)~f:(fun_keydata->let%sub()=Bonsai.Edge.lifecycle()~on_activate:(let%mapa=ainUi_effect.print_s[%messagea])inreturndata)incomponent|>Bonsai.Private.reveal_computation|>Bonsai.Private.pre_process|>sexp_of_packed_computation|>print_s;[%expect{|
(Sub
(from (Return (value Incr)))
(via 1)
(into (
Assoc
(map Incr)
(key_id 3)
(cmp_id 4)
(data_id 5)
(by (
Sub
(from (
Sub
(from (
Return (
value (
Mapn (
inputs ((
Mapn (
inputs ((
Mapn (inputs ((Mapn (inputs ((Named (uid 1)))))))))))))))))
(via 13)
(into (
Sub
(from (Return (value (Mapn (inputs ((Named (uid 13))))))))
(via 15)
(into (Lifecycle (value (Named (uid 15)))))))))
(via 16)
(into (
Sub
(from (Return (value (Mapn (inputs ((Named (uid 16))))))))
(via 18)
(into (Return (value (Named (uid 5)))))))))))) |}];;let%expect_test"constant_folding on assoc containing a dynamic_scope"=letdyn_var=Bonsai.Dynamic_scope.create~name:"dyn_var"~fallback:0()inletcomponent=Bonsai.assoc(moduleInt)(opaque_const_valueInt.Map.empty)~f:(fun_keydata->Bonsai.Dynamic_scope.setdyn_var(opaque_const_value1)~inside:(let%subx=Bonsai.Dynamic_scope.lookupdyn_varinreturn(Value.bothdatax)))incomponent|>Bonsai.Private.reveal_computation|>Bonsai.Private.pre_process|>sexp_of_packed_computation|>print_s;[%expect{|
(Assoc
(map Incr)
(key_id 2)
(cmp_id 3)
(data_id 4)
(by (
Store
(id 0)
(value Incr)
(inner (
Sub
(from (Fetch (id 0)))
(via 5)
(into (
Return (
value (
Mapn (
inputs (
(Named (uid 4))
(Named (uid 5))))))))))))) |}];;let%expect_test"on_display for updating a state (using on_change)"=letcallback=Value.return(funprevcur->Ui_effect.print_s[%message"change!"(prev:intoption)(cur:int)])inletcomponentinput=Bonsai.Edge.on_change'(moduleInt)~callbackinputinletvar=Bonsai.Var.create1inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=unitletsexp_of_t()=Sexp.Atom"rendering..."end))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{|
rendering...
(change! (prev ()) (cur 1)) |}];Handle.showhandle;[%expect{| rendering... |}];Handle.showhandle;[%expect{| rendering... |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{|
rendering...
(change! (prev (1)) (cur 2)) |}];Handle.showhandle;[%expect{| rendering... |}];Handle.showhandle;[%expect{| rendering... |}];;let%expect_test"actor"=letprint_int_effect=printf"%d\n"|>Bonsai.Effect.of_sync_funinletcomponent=let%sub_,effect=Bonsai.actor0(moduleInt)(moduleUnit)~default_model:0~recv:(fun~schedule_event:_v()->v+1,v)inreturn@@let%mapeffect=effectinlet%bind.Bonsai.Effecti=effect()inprint_int_effectiinlethandle=Handle.create(modulestructtypet=unitEffect.ttypeincoming=unitletview_=""letincomingt()=tend)componentinHandle.do_actionshandle[()];Handle.showhandle;[%expect{| 0 |}];Handle.do_actionshandle[();();()];Handle.showhandle;[%expect{|
1
2
3 |}];;let%expect_test"lifecycle"=leteffectactionon=Ui_effect.print_s[%message(action:string)(on:string)]|>Value.returninletcomponentinput=letrendered=Bonsai.const""inif%subinputthen(let%sub()=Bonsai.Edge.lifecycle~on_activate:(effect"activate""a")~on_deactivate:(effect"deactivate""a")~after_display:(effect"after-display""a")()inrendered)else(let%sub()=Bonsai.Edge.lifecycle~on_activate:(effect"activate""b")~on_deactivate:(effect"deactivate""b")~after_display:(effect"after-display""b")()inrendered)inletvar=Bonsai.Var.createtrueinlethandle=Handle.create(Result_spec.string(moduleString))(component(Bonsai.Var.valuevar))inHandle.showhandle;[%expect{|
((action activate) (on a))
((action after-display) (on a)) |}];Bonsai.Var.setvarfalse;Handle.showhandle;[%expect{|
((action deactivate) (on a))
((action activate) (on b))
((action after-display) (on b)) |}];Bonsai.Var.setvartrue;Handle.showhandle;[%expect{|
((action deactivate) (on b))
((action activate) (on a))
((action after-display) (on a)) |}];;let%test_module"Clock.every"=(modulestructlet%expect_test"Clocks that trigger immediately at the beginning"=letprint_hi=(fun()->print_endline"hi")|>Bonsai.Effect.of_sync_funinletclocks=[Bonsai.Clock.every~when_to_start_next_effect:`Every_multiple_of_period_blocking~trigger_on_activate:true(Time_ns.Span.of_sec3.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Wait_period_after_previous_effect_finishes_blocking~trigger_on_activate:true(Time_ns.Span.of_sec3.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Wait_period_after_previous_effect_starts_blocking~trigger_on_activate:true(Time_ns.Span.of_sec3.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Every_multiple_of_period_non_blocking~trigger_on_activate:true(Time_ns.Span.of_sec3.0)(Value.return(print_hi()))]inList.iterclocks~f:(funclock->lethandle=Handle.create(Result_spec.sexp(moduleUnit))clockinletmove_forward_and_show()=Handle.advance_clock_byhandle(Time_ns.Span.of_sec1.0);Handle.recompute_view_until_stablehandleinHandle.recompute_view_until_stablehandle;[%expect{| hi |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| hi |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| hi |}]);;let%expect_test"Clocks that wait span length before triggering at the beginning"=letprint_hi=(fun()->print_endline"hi")|>Bonsai.Effect.of_sync_funinletclocks=[Bonsai.Clock.every~when_to_start_next_effect:`Every_multiple_of_period_blocking~trigger_on_activate:false(Time_ns.Span.of_sec3.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Wait_period_after_previous_effect_finishes_blocking~trigger_on_activate:false(Time_ns.Span.of_sec3.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Wait_period_after_previous_effect_starts_blocking~trigger_on_activate:false(Time_ns.Span.of_sec3.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Every_multiple_of_period_non_blocking~trigger_on_activate:false(Time_ns.Span.of_sec3.0)(Value.return(print_hi()))]inList.iterclocks~f:(funclock->lethandle=Handle.create(Result_spec.sexp(moduleUnit))clockinletmove_forward_and_show()=Handle.advance_clock_byhandle(Time_ns.Span.of_sec1.0);Handle.recompute_view_until_stablehandleinHandle.recompute_view_until_stablehandle;[%expect{| |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| hi |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| |}];move_forward_and_show();[%expect{| hi |}]);;let%expect_test"Clocks that move with a span of 0"=letprint_hi=(fun()->print_endline"hi")|>Bonsai.Effect.of_sync_funinletclocks=[Bonsai.Clock.every~when_to_start_next_effect:`Every_multiple_of_period_blocking~trigger_on_activate:false(Time_ns.Span.of_sec0.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Wait_period_after_previous_effect_finishes_blocking~trigger_on_activate:false(Time_ns.Span.of_sec0.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Wait_period_after_previous_effect_starts_blocking~trigger_on_activate:false(Time_ns.Span.of_sec0.0)(Value.return(print_hi()));Bonsai.Clock.every~when_to_start_next_effect:`Every_multiple_of_period_non_blocking~trigger_on_activate:false(Time_ns.Span.of_sec0.0)(Value.return(print_hi()))]inList.iterclocks~f:(funclock->lethandle=Handle.create(Result_spec.sexp(moduleUnit))clockinletmove_forward_and_show()=Handle.advance_clock_byhandle(Time_ns.Span.nextTime_ns.Span.zero);Handle.recompute_view_until_stablehandleinHandle.recompute_view_until_stablehandle;[%expect{| hi |}];move_forward_and_show();[%expect{| hi |}];move_forward_and_show();[%expect{| hi |}];move_forward_and_show();[%expect{| hi |}];move_forward_and_show();[%expect{| hi |}];move_forward_and_show();[%expect{| hi |}];move_forward_and_show();[%expect{| hi |}]);;letcreate_clock_handle~start~svar~when_to_start_next_effect~trigger_on_activate~span=letaction=Value.return(let%bind.Effect()=(Effect.of_sync_fun(fun()->print_endline"[tick] - effect started"))()inlet%bind.Effect()=(Effect.For_testing.of_svar_fun(fun()->!svar))()inEffect.of_sync_fun(fun()->print_endline"[tock] - effect ended")())inletclock=Bonsai.Clock.every~when_to_start_next_effect~trigger_on_activate(Time_ns.Span.of_secspan)actioninHandle.create~start_time:(Time_ns.of_span_since_epoch(Time_ns.Span.of_secstart))(Result_spec.sexp(moduleUnit))clock;;letprint_timehandle=letclock=Handle.clockhandleinletnow=Ui_incr.Clock.nowclock|>Time_ns.to_string_abs_parts~zone:Time_float.Zone.utcinprint_endline(List.last_exnnow);;letmove_forward_and_show?(after_show=Fn.const())~handlespan=printf"before: ";print_timehandle;Handle.advance_clock_byhandle(Time_ns.Span.of_secspan);printf"after: ";print_timehandle;Handle.recompute_view_until_stablehandle;after_show();printf"after paint: ";print_timehandle;;letfill_and_reset_svar~svar=Effect.For_testing.Svar.fill_if_empty!svar();svar:=Effect.For_testing.Svar.create();;letadvance_and_clear_svar~handle~svartime=Handle.advance_clock_byhandle(Time_ns.Span.of_sectime);fill_and_reset_svar~svar;;let%expect_test"`Wait_period_after_previous_effect_finishes_blocking behavior"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_finishes_blocking~span:3.0~trigger_on_activate:falseinletmove_forward_and_show=move_forward_and_show~handleinHandle.recompute_view_until_stablehandle;move_forward_and_show1.0;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:08.000000000Z
after paint: 00:00:08.000000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)2.0;[%expect{|
before: 00:00:08.000000000Z
after: 00:00:10.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:10.200000000Z |}];(* Does not trigger at 7s + 2 * 3s. *)move_forward_and_show2.8;[%expect{|
before: 00:00:10.200000000Z
after: 00:00:13.000000000Z
after paint: 00:00:13.000000000Z |}];(* Triggers at 7s (initial) + 3s (first tick) + 0.2s (time taken by first tick) + 3s
(time after first click)*)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)0.2;[%expect{|
before: 00:00:13.000000000Z
after: 00:00:13.200000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:13.400000000Z |}];(* Starting next trigger without immediately finishing/filling the svar. *)move_forward_and_show3.0;[%expect{|
before: 00:00:13.400000000Z
after: 00:00:16.400000000Z
[tick] - effect started
after paint: 00:00:16.400000000Z |}];(* Clock does not trigger before the current action is completed. *)move_forward_and_show3.0;[%expect{|
before: 00:00:16.400000000Z
after: 00:00:19.400000000Z
after paint: 00:00:19.400000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:19.400000000Z
after: 00:00:22.400000000Z
after paint: 00:00:22.400000000Z |}];fill_and_reset_svar~svar;[%expect{| [tock] - effect ended |}];move_forward_and_show2.9;[%expect{|
before: 00:00:22.400000000Z
after: 00:00:25.300000000Z
after paint: 00:00:25.300000000Z |}];move_forward_and_show0.1;[%expect{|
before: 00:00:25.300000000Z
after: 00:00:25.400000000Z
[tick] - effect started
after paint: 00:00:25.400000000Z |}];;let%expect_test"`Wait_period_after_previous_effect_starts_blocking behavior"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_starts_blocking~span:3.0inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show1.0;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:08.000000000Z
after paint: 00:00:08.000000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)2.0;[%expect{|
before: 00:00:08.000000000Z
after: 00:00:10.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:10.200000000Z |}];(* Triggers at 7s + 6.0s unlike the
`Wait_period_after_previous_effect_finishes_blocking version of this
which would need to wait until 7s + 6.2s. *)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)2.8;[%expect{|
before: 00:00:10.200000000Z
after: 00:00:13.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:13.200000000Z |}];(* The next trigger will take a long time, 10 seconds! There will be a couple of
missed [ticks] and missed [tocks]. *)move_forward_and_show3.0;[%expect{|
before: 00:00:13.200000000Z
after: 00:00:16.200000000Z
[tick] - effect started
after paint: 00:00:16.200000000Z |}];(* Clock does not tick in before the previous action is complete. *)move_forward_and_show3.0;[%expect{|
before: 00:00:16.200000000Z
after: 00:00:19.200000000Z
after paint: 00:00:19.200000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:19.200000000Z
after: 00:00:22.200000000Z
after paint: 00:00:22.200000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:22.200000000Z
after: 00:00:25.200000000Z
after paint: 00:00:25.200000000Z |}];move_forward_and_show1.0;[%expect{|
before: 00:00:25.200000000Z
after: 00:00:26.200000000Z
after paint: 00:00:26.200000000Z |}];fill_and_reset_svar~svar;[%expect{| [tock] - effect ended |}];(* Time moves slightly forward which results in another trigger. (hence the
`Wait_period_after_previous_effect_starts_blocking behavior on skips. )*)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)0.01;[%expect{|
before: 00:00:26.200000000Z
after: 00:00:26.210000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:26.410000000Z |}];(* Next expected trigger is at 7s + 19.21s + 3s, so going to 7s + 22.11s should not
trigger. *)move_forward_and_show2.7;[%expect{|
before: 00:00:26.410000000Z
after: 00:00:29.110000000Z
after paint: 00:00:29.110000000Z |}];(* Trigger occurs at 7s + 22.21s as expected! 1*)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)0.1;[%expect{|
before: 00:00:29.110000000Z
after: 00:00:29.210000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:29.410000000Z |}];;let%test_module"Resilience against bugs from action time being equal to span time"=(modulestruct(* This test is the only one that initially presented a race condition. Although
the other kinds of clocks' implementations did not have a race condition when first implemented,
they are still tested in this module.*)let%expect_test_=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_starts_blocking~span:3.0inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show3.0;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:10.000000000Z
[tick] - effect started
after paint: 00:00:10.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:10.000000000Z
after: 00:00:13.000000000Z
after paint: 00:00:13.000000000Z |}];fill_and_reset_svar~svar;[%expect{| [tock] - effect ended |}];move_forward_and_show0.000001;[%expect{|
before: 00:00:13.000000000Z
after: 00:00:13.000001000Z
[tick] - effect started
after paint: 00:00:13.000001000Z |}];;let%expect_test_=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_finishes_blocking~span:3.0inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show3.;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:10.000000000Z
[tick] - effect started
after paint: 00:00:10.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:10.000000000Z
after: 00:00:13.000000000Z
after paint: 00:00:13.000000000Z |}];fill_and_reset_svar~svar;[%expect{| [tock] - effect ended |}];move_forward_and_show0.000001;[%expect{|
before: 00:00:13.000000000Z
after: 00:00:13.000001000Z
after paint: 00:00:13.000001000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:13.000001000Z
after: 00:00:16.000001000Z
[tick] - effect started
after paint: 00:00:16.000001000Z |}];;let%expect_test_=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:true~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_finishes_blocking~span:3.0inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;Handle.recompute_viewhandle;[%expect{|
()
[tick] - effect started |}];move_forward_and_show3.;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:10.000000000Z
after paint: 00:00:10.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:10.000000000Z
after: 00:00:13.000000000Z
after paint: 00:00:13.000000000Z |}];fill_and_reset_svar~svar;[%expect{| [tock] - effect ended |}];Handle.recompute_view_until_stablehandle;;let%expect_test"Next multiple clock"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_starts_blocking~span:3.0inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show3.0;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:10.000000000Z
[tick] - effect started
after paint: 00:00:10.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:10.000000000Z
after: 00:00:13.000000000Z
after paint: 00:00:13.000000000Z |}];fill_and_reset_svar~svar;[%expect{| [tock] - effect ended |}];move_forward_and_show0.000000001;[%expect{|
before: 00:00:13.000000000Z
after: 00:00:13.000000001Z
[tick] - effect started
after paint: 00:00:13.000000001Z |}];;end);;let%expect_test"`Every_multiple_of_period_blocking clock skip behavior"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Every_multiple_of_period_blocking~span:3.0inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show1.0;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:08.000000000Z
after paint: 00:00:08.000000000Z |}];(* `Every_multiple_of_period_blocking clock triggers on every t where [(t % span) = (init_time % span)]
Since initial time is 7s, the clock will trigger on every multiple of 3,
but offset by 1, so on 10s, 13s, 15s independent of skips.
*)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)2.0;[%expect{|
before: 00:00:08.000000000Z
after: 00:00:10.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:10.200000000Z |}];move_forward_and_show2.7;[%expect{|
before: 00:00:10.200000000Z
after: 00:00:12.900000000Z
after paint: 00:00:12.900000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)0.1;[%expect{|
before: 00:00:12.900000000Z
after: 00:00:13.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:13.200000000Z |}];move_forward_and_show2.8;[%expect{|
before: 00:00:13.200000000Z
after: 00:00:16.000000000Z
[tick] - effect started
after paint: 00:00:16.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:16.000000000Z
after: 00:00:19.000000000Z
after paint: 00:00:19.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:19.000000000Z
after: 00:00:22.000000000Z
after paint: 00:00:22.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:22.000000000Z
after: 00:00:25.000000000Z
after paint: 00:00:25.000000000Z |}];move_forward_and_show1.0;[%expect{|
before: 00:00:25.000000000Z
after: 00:00:26.000000000Z
after paint: 00:00:26.000000000Z |}];fill_and_reset_svar~svar;[%expect{| [tock] - effect ended |}];move_forward_and_show0.1;[%expect{|
before: 00:00:26.000000000Z
after: 00:00:26.100000000Z
after paint: 00:00:26.100000000Z |}];move_forward_and_show1.8;[%expect{|
before: 00:00:26.100000000Z
after: 00:00:27.900000000Z
after paint: 00:00:27.900000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)0.1;[%expect{|
before: 00:00:27.900000000Z
after: 00:00:28.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:28.200000000Z |}];;let%expect_test"`Every_multiple_of_period_non_blocking clock skip behavior"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Every_multiple_of_period_non_blocking~span:3.0inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show1.0;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:08.000000000Z
after paint: 00:00:08.000000000Z |}];(* `Every_multiple_of_period_blocking clock triggers on every t where [(t % span) = (init_time % span)]
Since initial time is 7s, the clock will trigger on every multiple of 3,
but offset by 1, so on 10s, 13s, 15s independent of skips.
*)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)2.0;[%expect{|
before: 00:00:08.000000000Z
after: 00:00:10.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:10.200000000Z |}];move_forward_and_show2.7;[%expect{|
before: 00:00:10.200000000Z
after: 00:00:12.900000000Z
after paint: 00:00:12.900000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)0.1;[%expect{|
before: 00:00:12.900000000Z
after: 00:00:13.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:13.200000000Z |}];move_forward_and_show2.8;[%expect{|
before: 00:00:13.200000000Z
after: 00:00:16.000000000Z
[tick] - effect started
after paint: 00:00:16.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:16.000000000Z
after: 00:00:19.000000000Z
[tick] - effect started
after paint: 00:00:19.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:19.000000000Z
after: 00:00:22.000000000Z
[tick] - effect started
after paint: 00:00:22.000000000Z |}];move_forward_and_show3.0;[%expect{|
before: 00:00:22.000000000Z
after: 00:00:25.000000000Z
[tick] - effect started
after paint: 00:00:25.000000000Z |}];move_forward_and_show1.0;[%expect{|
before: 00:00:25.000000000Z
after: 00:00:26.000000000Z
after paint: 00:00:26.000000000Z |}];fill_and_reset_svar~svar;[%expect{|
[tock] - effect ended
[tock] - effect ended
[tock] - effect ended
[tock] - effect ended |}];move_forward_and_show0.1;[%expect{|
before: 00:00:26.000000000Z
after: 00:00:26.100000000Z
after paint: 00:00:26.100000000Z |}];move_forward_and_show1.8;[%expect{|
before: 00:00:26.100000000Z
after: 00:00:27.900000000Z
after paint: 00:00:27.900000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.2)0.1;[%expect{|
before: 00:00:27.900000000Z
after: 00:00:28.000000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:28.200000000Z |}];;let%test_module"Resilience against inactive clocks"=(modulestructlet%expect_test_=letprint_sanitized_dropped_action_if_needed=Effect.of_sync_fun(fun()->letout=[%expect.output]inifString.is_emptyoutthen()elseprint_endline"[Whoops! An action was dropped!]")inList.iter[`Wait_period_after_previous_effect_starts_blocking;`Wait_period_after_previous_effect_finishes_blocking;`Every_multiple_of_period_blocking;`Every_multiple_of_period_non_blocking]~f:(funwhen_to_start_next_effect->letcomponent=let%substate,set_state=Bonsai.state(moduleBool)~default_model:trueinmatch%substatewith|true->let%sub()=Bonsai.Clock.every~when_to_start_next_effect~trigger_on_activate:false(Time_ns.Span.of_sec3.0)(let%mapset_state=set_stateinlet%bind.Effect()=(Effect.of_sync_fun(fun()->print_endline"[tick tock] - (state := false)"))()inlet%bind.Effect()=set_statefalseinprint_sanitized_dropped_action_if_needed())inBonsai.consttrue|false->let%sub()=Bonsai.Edge.after_display(let%mapset_state=set_stateinlet%bind.Effect()=(Effect.of_sync_fun(fun()->print_endline"(state := true)"))()inlet%bind.Effect()=set_statetrueinprint_sanitized_dropped_action_if_needed())inBonsai.constfalseinletstart=Time_ns.of_span_since_epoch(Time_ns.Span.of_min1.0)inlethandle=Handle.create(Result_spec.sexp(moduleBool))~start_time:startcomponentinletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| true |}];move_forward_and_show3.0;[%expect{|
[Whoops! An action was dropped!]
after paint: 00:01:03.000000000Z |}];Handle.showhandle;[%expect{| true |}];move_forward_and_show3.0;[%expect{|
[Whoops! An action was dropped!]
after paint: 00:01:06.000000000Z |}];Handle.showhandle;[%expect{| true |}];move_forward_and_show3.0;[%expect{|
[Whoops! An action was dropped!]
after paint: 00:01:09.000000000Z |}];Handle.showhandle;[%expect{| true |}];move_forward_and_show3.0;[%expect{|
[Whoops! An action was dropped!]
after paint: 00:01:12.000000000Z |}];Handle.showhandle;[%expect{| true |}]);;end);;let%test_module"Super small timespans on clock"=(modulestructlet%expect_test_=List.iter[`Every_multiple_of_period_blocking;`Wait_period_after_previous_effect_finishes_blocking;`Wait_period_after_previous_effect_starts_blocking;`Every_multiple_of_period_non_blocking]~f:(funwhen_to_start_next_effect->letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:0.0~svar~when_to_start_next_effect~span:0.01inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:00.000000000Z
after: 00:00:00.010000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:00.010000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:00.010000000Z
after: 00:00:00.020000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:00.020000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:00.020000000Z
after: 00:00:00.030000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:00.030000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:00.030000000Z
after: 00:00:00.040000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:00.040000000Z |}]);;let%expect_test_=List.iter[`Every_multiple_of_period_blocking;`Wait_period_after_previous_effect_finishes_blocking;`Wait_period_after_previous_effect_starts_blocking;`Every_multiple_of_period_non_blocking]~f:(funwhen_to_start_next_effect->letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:0.0~svar~when_to_start_next_effect~span:0.01inletmove_forward_and_show?(after_show=Fn.const())~handlespan=printf"before: ";print_timehandle;Handle.advance_clock_byhandle(Time_ns.Span.of_secspan);printf"after: ";print_timehandle;Handle.showhandle;(* Advancing the clock by one second (many time the clock's time span) before recomputing. *)Handle.advance_clock_byhandle(Time_ns.Span.of_sec1.0);Handle.recompute_viewhandle;after_show();printf"after paint: ";print_timehandleinletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:00.000000000Z
after: 00:00:00.010000000Z
()
[tick] - effect started
[tock] - effect ended
after paint: 00:00:01.010000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:01.010000000Z
after: 00:00:01.020000000Z
()
[tick] - effect started
[tock] - effect ended
after paint: 00:00:02.020000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:02.020000000Z
after: 00:00:02.030000000Z
()
[tick] - effect started
[tock] - effect ended
after paint: 00:00:03.030000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.)0.01;[%expect{|
before: 00:00:03.030000000Z
after: 00:00:03.040000000Z
()
[tick] - effect started
[tock] - effect ended
after paint: 00:00:04.040000000Z |}]);;let%expect_test"`Wait_period_after_previous_effect_finishes_blocking skip \
behavior"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_finishes_blocking~span:0.01inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show0.005;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:07.005000000Z
after paint: 00:00:07.005000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.005;[%expect{|
before: 00:00:07.005000000Z
after: 00:00:07.010000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:07.012000000Z |}];(* Does not trigger at 7s + 2 * 0.01. *)move_forward_and_show0.008;[%expect{|
before: 00:00:07.012000000Z
after: 00:00:07.020000000Z
after paint: 00:00:07.020000000Z |}];(* Triggers at 7s (initial) + 0.01s (first tick) + 0.002s (time taken by first tick) + 0.001s
(time after first click)*)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.002;[%expect{|
before: 00:00:07.020000000Z
after: 00:00:07.022000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:07.024000000Z |}];;let%expect_test"`Wait_period_after_previous_effect_starts_blocking skip behavior"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Wait_period_after_previous_effect_starts_blocking~span:0.01inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show0.005;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:07.005000000Z
after paint: 00:00:07.005000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.005;[%expect{|
before: 00:00:07.005000000Z
after: 00:00:07.010000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:07.012000000Z |}];(* Triggers at 7s + 2 * 0.01s unlike the "minimum" version of this which would need to wait
until 7s + 2 * 0.01s + 0.002s. *)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.008;[%expect{|
before: 00:00:07.012000000Z
after: 00:00:07.020000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:07.022000000Z |}];(* The next trigger will take a long time, 10 seconds! There will be a couple of
missed [ticks] and missed [tocks]. *)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar10.)0.008;[%expect{|
before: 00:00:07.022000000Z
after: 00:00:07.030000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:17.030000000Z |}];(* Time moves slightly forward which results in another trigger. (hence the
`Wait_period_after_previous_effect_starts_blocking behavior on skips. )*)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.00001;[%expect{|
before: 00:00:17.030000000Z
after: 00:00:17.030010000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:17.032010000Z |}];move_forward_and_show0.007;[%expect{|
before: 00:00:17.032010000Z
after: 00:00:17.039010000Z
after paint: 00:00:17.039010000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.001;[%expect{|
before: 00:00:17.039010000Z
after: 00:00:17.040010000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:17.042010000Z |}];;let%expect_test"`Every_multiple_of_period_blocking behavior"=letsvar=ref(Effect.For_testing.Svar.create())inlethandle=create_clock_handle~trigger_on_activate:false~start:7.0~svar~when_to_start_next_effect:`Every_multiple_of_period_blocking~span:0.01inletmove_forward_and_show=move_forward_and_show~handleinHandle.showhandle;[%expect{| () |}];move_forward_and_show0.005;[%expect{|
before: 00:00:07.000000000Z
after: 00:00:07.005000000Z
after paint: 00:00:07.005000000Z |}];(* Clock triggers on every t where [(t % span) = (init_time % span)]
Since initial time is 7s, the clock will trigger on every multiple of 3,
but offset by 1, so on 10s, 13s, 16s independent of skips. *)move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.005;[%expect{|
before: 00:00:07.005000000Z
after: 00:00:07.010000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:07.012000000Z |}];move_forward_and_show0.007;[%expect{|
before: 00:00:07.012000000Z
after: 00:00:07.019000000Z
after paint: 00:00:07.019000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.001;[%expect{|
before: 00:00:07.019000000Z
after: 00:00:07.020000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:07.022000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar10.0)0.008;[%expect{|
before: 00:00:07.022000000Z
after: 00:00:07.030000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:17.030000000Z |}];move_forward_and_show0.001;[%expect{|
before: 00:00:17.030000000Z
after: 00:00:17.031000000Z
after paint: 00:00:17.031000000Z |}];move_forward_and_show0.008;[%expect{|
before: 00:00:17.031000000Z
after: 00:00:17.039000000Z
after paint: 00:00:17.039000000Z |}];move_forward_and_show~after_show:(fun()->advance_and_clear_svar~handle~svar0.002)0.001;[%expect{|
before: 00:00:17.039000000Z
after: 00:00:17.040000000Z
[tick] - effect started
[tock] - effect ended
after paint: 00:00:17.042000000Z |}];;end);;let%expect_test{| [every] continues to trigger effects even when the action takes a long time |}=letmatch_var=Bonsai.Var.createtrueinletcomponent=let%sub(),inject=let%subclock=Bonsai.Incr.with_clockIncr.returninBonsai.state_machine1(moduleUnit)(moduleUnit)~default_model:()~apply_action:(fun~inject:_~schedule_eventclock()()->matchclockwith|Activeclock->schedule_event(Effect.of_sync_fun(Incr.Clock.advance_clock_byclock)(Time_ns.Span.of_sec5.0));print_endline"did action"|Inactive->print_endline"inactive")clockinmatch%subBonsai.Var.valuematch_varwith|true->Bonsai.Clock.every~when_to_start_next_effect:`Every_multiple_of_period_non_blocking~trigger_on_activate:true(Time_ns.Span.of_sec3.0)(let%mapinject=injectininject())|false->Bonsai.const()inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.recompute_viewhandle;[%expect{| |}];Handle.recompute_viewhandle;[%expect{| did action |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec5.0);Handle.recompute_viewhandle;[%expect{| did action |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec5.0);Handle.recompute_viewhandle;[%expect{| did action |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec5.0);Handle.recompute_viewhandle;[%expect{| did action |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec5.0);Handle.recompute_viewhandle;[%expect{| did action |}];;end);;letedge_poll_shared~get_expect_output=leteffect_tracker=Query_response_tracker.create()inleteffect=Bonsai.Effect.For_testing.of_query_response_trackereffect_trackerinletvar=Bonsai.Var.create"hello"inletcomponent=Bonsai.Edge.Poll.effect_on_change(moduleString)(moduleString)Bonsai.Edge.Poll.Starting.empty(Bonsai.Var.valuevar)~effect:(Value.returneffect)inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=stringoption[@@derivingsexp]end))componentinlettrigger_display()=(* Polling is driven by [on_display] callbacks, which is triggered by
[Handle.show] *)Handle.showhandle;letpending=Query_response_tracker.queries_pending_responseeffect_trackerinletoutput=Sexp.of_string(get_expect_output())inprint_s[%message(pending:stringlist)(output:Sexp.t)]invar,effect_tracker,trigger_display;;let%expect_test"Edge.poll in order"=letget_expect_output()=[%expect.output]inletvar,effect_tracker,trigger_display=edge_poll_shared~get_expect_outputintrigger_display();[%expect{|
((pending ())
(output ())) |}];trigger_display();[%expect{|
((pending (hello)) (output ())) |}];Bonsai.Var.setvar"world";trigger_display();[%expect{|
((pending (hello)) (output ())) |}];trigger_display();[%expect{|
((pending (world hello)) (output ())) |}];Query_response_tracker.maybe_respondeffect_tracker~f:(funs->Respond(String.uppercases));trigger_display();[%expect{| ((pending ()) (output (WORLD))) |}];;(* When completing the requests out-of-order, the last-fired effect still
wins *)let%expect_test"Edge.poll out of order"=letget_expect_output()=[%expect.output]inletvar,effect_tracker,trigger_display=edge_poll_shared~get_expect_outputintrigger_display();[%expect{|
((pending ())
(output ())) |}];trigger_display();[%expect{|
((pending (hello)) (output ())) |}];Bonsai.Var.setvar"world";trigger_display();[%expect{|
((pending (hello)) (output ())) |}];trigger_display();[%expect{|
((pending (world hello)) (output ())) |}];Query_response_tracker.maybe_respondeffect_tracker~f:(function|"world"ass->Respond(String.uppercases)|_->No_response_yet);trigger_display();[%expect{|
((pending (hello))
(output (WORLD))) |}];Query_response_tracker.maybe_respondeffect_tracker~f:(function|"hello"ass->Respond(String.uppercases)|_->No_response_yet);trigger_display();[%expect{|
((pending ()) (output (WORLD))) |}];;let%expect_test"Clock.now"=letcomponent=Bonsai.Clock.nowinlethandle=Handle.create(Result_spec.sexp(moduleTime_ns.Alternate_sexp))componentinHandle.showhandle;[%expect{| "1970-01-01 00:00:00Z" |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec0.5);Handle.showhandle;[%expect{| "1970-01-01 00:00:00.5Z" |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec0.7);Handle.showhandle;[%expect{| "1970-01-01 00:00:01.2Z" |}];;let%expect_test"Clock.now"=letcomponent=let%subget_time=Bonsai.Clock.get_current_timeinBonsai.Edge.after_display(let%mapget_time=get_timeinlet%bind.Effectnow=get_timeinEffect.print_s[%sexp(now:Time_ns.Alternate_sexp.t)])inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.recompute_viewhandle;[%expect{| "1970-01-01 00:00:00Z" |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec0.5);Handle.recompute_viewhandle;[%expect{| "1970-01-01 00:00:00.5Z" |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec0.7);Handle.recompute_viewhandle;[%expect{| "1970-01-01 00:00:01.2Z" |}];;let%expect_test"Clock.approx_now"=letcomponent=Bonsai.Clock.approx_now~tick_every:(Time_ns.Span.of_sec1.0)inlethandle=Handle.create(Result_spec.sexp(moduleTime_ns.Alternate_sexp))componentinHandle.showhandle;[%expect{| "1970-01-01 00:00:00Z" |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec0.5);Handle.showhandle;[%expect{| "1970-01-01 00:00:00Z" |}];Handle.advance_clock_byhandle(Time_ns.Span.of_sec0.7);Handle.showhandle;[%expect{| "1970-01-01 00:00:01.2Z" |}];;(* $MDX part-begin=chain-computation *)letchain_computation=let%suba=Bonsai.const"x"inlet%subb,set_b=Bonsai.state(moduleString)~default_model:" "inlet%subc,set_c=Bonsai.state(moduleString)~default_model:" "inlet%subd,set_d=Bonsai.state(moduleString)~default_model:" "inlet%sub()=Bonsai.Edge.on_change(moduleString)a~callback:set_binlet%sub()=Bonsai.Edge.on_change(moduleString)b~callback:set_cinlet%sub()=Bonsai.Edge.on_change(moduleString)c~callback:set_dinreturn(Value.map4abcd~f:(sprintf"a:%s b:%s c:%s d:%s"));;(* $MDX part-end *)(* $MDX part-begin=chained-on-change *)let%expect_test"chained on_change"=lethandle=Handle.create(Result_spec.string(moduleString))chain_computationinHandle.showhandle;[%expect{| a:x b: c: d: |}];Handle.showhandle;[%expect{| a:x b:x c: d: |}];Handle.showhandle;[%expect{| a:x b:x c:x d: |}];Handle.showhandle;[%expect{| a:x b:x c:x d:x |}];Handle.showhandle;[%expect{| a:x b:x c:x d:x |}];;(* $MDX part-end *)(* $MDX part-begin=chained-on-change-recompute *)let%expect_test"chained on_change with recompute_view_until_stable"=lethandle=Handle.create(Result_spec.string(moduleString))chain_computationinHandle.recompute_view_until_stablehandle;Handle.showhandle;[%expect{| a:x b:x c:x d:x |}];;(* $MDX part-end *)let%expect_test"infinite chain!"=letcomputation=let%substate,set_state=Bonsai.state(moduleInt)~default_model:0inletcallback=let%mapset_state=set_stateinfunnew_state->set_state(new_state+1)inlet%sub()=Bonsai.Edge.on_change(moduleInt)state~callbackinBonsai.const()inlethandle=Handle.create(Result_spec.string(moduleUnit))computationinExpect_test_helpers_base.require_does_raise[%here](fun()->Handle.recompute_view_until_stablehandle);[%expect{| (Failure "view not stable after 100 recomputations") |}];;let%expect_test"computation.all_map"=letcomponent=let%map.Computationmap=[1,Bonsai.const"a";2,Bonsai.const"b"]|>Int.Map.of_alist_exn|>Computation.all_mapin[%sexp_of:stringInt.Map.t]mapinlethandle=Handle.create(Result_spec.string(moduleSexp))componentinHandle.showhandle;[%expect{| ((1 a)(2 b)) |}];;let%expect_test"dynamic lookup"=letid=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no"()inletcomponent=Bonsai.Dynamic_scope.setid(Value.return"hello")~inside:(Bonsai.Dynamic_scope.lookupid)inlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| hello |}];;let%expect_test"dynamic lookup fails"=letid=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no"()inletcomponent=Bonsai.Dynamic_scope.lookupidinlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| no |}];;let%expect_test"eval inside one, use inside another"=letid=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no"()inletcomponent=let%suba=Bonsai.Dynamic_scope.setid(Value.return"hello")~inside:(Bonsai.Dynamic_scope.lookupid)inlet%subb=Bonsai.Dynamic_scope.setid(Value.return"world")~inside:(Bonsai.reada)inreturnbinlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| hello |}];;let%expect_test"sub outside, use inside"=letid=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no"()inletcomponent=let%subfind=Bonsai.Dynamic_scope.lookupidinBonsai.Dynamic_scope.setid(Value.return"hello")~inside:(returnfind)inlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| no |}];;let%expect_test"use resetter"=letid=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no"()inletcomponent=Bonsai.Dynamic_scope.set'id(Value.return"hello")~f:(fun{revert}->revert(Bonsai.Dynamic_scope.lookupid))inlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| no |}];;let%expect_test"nested resetter"=letid=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no"()inletcomponent=Bonsai.Dynamic_scope.setid(Value.return"hello")~inside:(Bonsai.Dynamic_scope.set'id(Value.return"world")~f:(fun{revert}->revert(Bonsai.Dynamic_scope.lookupid)))inlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| hello |}];;let%expect_test"resetter only impacts the id you target"=letid_a=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no-a"()inletid_b=Bonsai.Dynamic_scope.create~name:"my-id"~fallback:"no-b"()inletcomponent=Bonsai.Dynamic_scope.set'id_a(Value.return"hello")~f:(fun{revert}->Bonsai.Dynamic_scope.setid_b(Value.return"world")~inside:(revert@@let%suba=Bonsai.Dynamic_scope.lookupid_ainlet%subb=Bonsai.Dynamic_scope.lookupid_binreturn(Value.map2ab~f:(funab->a^" "^b))))inlethandle=Handle.create(Result_spec.string(moduleString))componentinHandle.showhandle;[%expect{| no-a world |}];;moduleM=structtypet={a:string;b:int}[@@derivingsexp_of,fields]endlet%expect_test"derived value"=letid=Bonsai.Dynamic_scope.create~sexp_of:M.sexp_of_t~name:"my-id"~fallback:{a="hi";b=5}()inleta=Bonsai.Dynamic_scope.derivedid~get:M.a~set:(Field.fsetM.Fields.a)inletcomponent=Bonsai.Dynamic_scope.seta(Value.return"hello")~inside:(Bonsai.Dynamic_scope.lookupid)inlethandle=Handle.create(Result_spec.sexp(moduleM))componentinHandle.showhandle;[%expect{| ((a hello) (b 5)) |}];;let%expect_test"derived value revert"=letid=Bonsai.Dynamic_scope.create~sexp_of:M.sexp_of_t~name:"my-id"~fallback:{a="hi";b=5}()inleta=Bonsai.Dynamic_scope.derivedid~get:M.a~set:(Field.fsetM.Fields.a)inletcomponent=Bonsai.Dynamic_scope.set'a(Value.return"hello")~f:(fun{revert}->revert(Bonsai.Dynamic_scope.lookupid))inlethandle=Handle.create(Result_spec.sexp(moduleM))componentinHandle.showhandle;[%expect{| ((a hi) (b 5)) |}];;let%expect_test"derived value nested revert inner"=letid=Bonsai.Dynamic_scope.create~sexp_of:M.sexp_of_t~name:"my-id"~fallback:{a="hi";b=5}()inleta=Bonsai.Dynamic_scope.derivedid~get:M.a~set:(Field.fsetM.Fields.a)inletcomponent=Bonsai.Dynamic_scope.seta(Value.return"hello")~inside:(Bonsai.Dynamic_scope.set'a(Value.return"world")~f:(fun{revert}->revert(Bonsai.Dynamic_scope.lookupid)))inlethandle=Handle.create(Result_spec.sexp(moduleM))componentinHandle.showhandle;[%expect{| ((a hello) (b 5)) |}];;let%expect_test"derived value nested revert outer"=letid=Bonsai.Dynamic_scope.create~sexp_of:M.sexp_of_t~name:"my-id"~fallback:{a="hi";b=5}()inleta=Bonsai.Dynamic_scope.derivedid~get:M.a~set:(Field.fsetM.Fields.a)inletb=Bonsai.Dynamic_scope.derivedid~get:M.b~set:(Field.fsetM.Fields.b)inletcomponent=Bonsai.Dynamic_scope.set'a(Value.return"hello")~f:(fun{revert}->Bonsai.Dynamic_scope.setb(Value.return1000)~inside:(revert(Bonsai.Dynamic_scope.lookupid)))inlethandle=Handle.create(Result_spec.sexp(moduleM))componentinHandle.showhandle;[%expect{| ((a hi) (b 1000)) |}];;let%expect_test"exactly once"=letcomponent=Bonsai_extra.exactly_once(Bonsai.Value.return(Ui_effect.print_s[%message"hello!"]))inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.showhandle;[%expect{|
()
hello! |}];Handle.showhandle;[%expect{| () |}];;let%expect_test"exactly once with value"=letcomponent=Bonsai_extra.exactly_once_with_value(moduleString)(Bonsai.Value.return(let%bind.Ui_effect()=Ui_effect.print_s[%message"hello!"]inUi_effect.return"done"))inlethandle=Handle.create(Result_spec.sexp(modulestructtypet=stringoption[@@derivingsexp,equal]end))componentinHandle.showhandle;[%expect{|
()
hello! |}];Handle.showhandle;[%expect{| (done) |}];;let%expect_test"yoink"=letcomponent=let%substate,set_state=Bonsai.state(moduleInt)~default_model:0inlet%subget_state=Bonsai.yoinkstateinBonsai_extra.exactly_once(let%mapget_state=get_stateandset_state=set_stateinlet%bind.Bonsai.Effect()=set_state1inlet%bind.Bonsai.Effects=match%bind.Effectget_statewith|Actives->Effect.returns|Inactive->Effect.neverinUi_effect.print_s[%message(s:int)])inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.showhandle;[%expect{| () |}];Handle.showhandle;[%expect{|
(s 1)
() |}];;let%expect_test"freeze"=letvar=Bonsai.Var.create"hello"inletcomponent=Bonsai.freeze(moduleString)(Bonsai.Var.valuevar)inlethandle=Handle.create(Result_spec.sexp(moduleString))componentinHandle.showhandle;[%expect{| hello |}];Bonsai.Var.setvar"world";Handle.showhandle;[%expect{| hello |}];;let%expect_test"effect-lazy"=letmessage=Bonsai.Var.create"hello"inleton=Bonsai.Var.createtrueinletcomponent=let%subon_deactivate=let%arrmessage=Bonsai.Var.valuemessageinleta=print_endline"computing a...";Effect.print_s[%sexp"a",(message:string)]inletb=Effect.lazy_(lazy(print_endline"computing b...";Effect.print_s[%sexp"b",(message:string)]))inEffect.Many[a;b]inif%subBonsai.Var.valueonthenBonsai.Edge.lifecycle~on_deactivate()elseBonsai.const()inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.showhandle;Bonsai.Var.setmessage"there";Handle.showhandle;Bonsai.Var.setmessage"world";Handle.showhandle;[%expect{|
computing a...
()
computing a...
()
computing a...
() |}];Bonsai.Var.setonfalse;Handle.showhandle;[%expect{|
()
(a world)
computing b...
(b world) |}];;let%expect_test"id_gen"=letmoduleId=Bonsai_extra.Id_gen(Int)()inletcomponent=let%subnext=Id.componentinBonsai.Edge.after_display(let%mapnext=nextinlet%bind.Bonsai.Effectid=nextinUi_effect.print_s[%sexp(id:Id.t)])inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.recompute_viewhandle;Handle.recompute_viewhandle;Handle.recompute_viewhandle;Handle.recompute_viewhandle;Handle.recompute_viewhandle;[%expect{|
0
1
2
3 |}];;let%expect_test"with_self_effect"=letmoduleResult_spec=structtypeaction=|Setofint|Printtypet=string*(action->unitEffect.t)[@@derivingsexp]typeincoming=actionletequal=phys_equalletview(result,_)=resultletincoming(_,self_effect)=self_effectendinletcomponent=Bonsai_extra.with_self_effect(moduleResult_spec)~f:(funinput->let%substate=Bonsai.state(moduleInt)~default_model:0inlet%arrnumber,set_number=stateandinput=inputinleteffectaction=matchactionwith|Result_spec.Print->(match%bind.Effectinputwith|Active(computed,(_:Result_spec.action->unitEffect.t))->Effect.print_s[%message"Active"(computed:string)]|Inactive->Effect.print_s[%message"Inactive"])|Seti->set_numberiinletcomputed=sprintf"the value: [%d]"numberincomputed,effect)inlethandle=Handle.create(moduleResult_spec)componentinHandle.showhandle;[%expect{| the value: [0] |}];Handle.do_actionshandle[Print];[%expect{||}];Handle.showhandle;[%expect{|
(Active (computed "the value: [0]"))
the value: [0] |}];Handle.do_actionshandle[Set1];Handle.showhandle;[%expect{| the value: [1] |}];Handle.do_actionshandle[Set5;Print;Set6;Print];Handle.showhandle;[%expect{|
(Active (computed "the value: [5]"))
(Active (computed "the value: [6]"))
the value: [6] |}];;let%expect_test"state_machine_dynamic_model"=letcomponent=Bonsai_extra.state_machine0_dynamic_model(moduleString)(moduleString)~model:(`Computed(Bonsai.Value.return(function|None->"not set "|Somes->sprintf"set %s"s)))~apply_action:(fun~inject:_~schedule_event:__modelaction->action)inlethandle=Handle.create(modulestructtypet=string*(string->unitEffect.t)typeincoming=stringletview(s,_)=sletincoming(_,s)=send)componentinHandle.showhandle;[%expect{| not set |}];Handle.do_actionshandle["here"];Handle.showhandle;[%expect{| set here |}];;let%expect_test"portal"=letvar=Bonsai.Var.create(Sexp.Atom"hello")inletcomponent=Bonsai_extra.with_inject_fixed_point(funinject->let%sub()=Bonsai.Edge.on_change(moduleSexp)(Bonsai.Var.valuevar)~callback:injectinBonsai.const((),Ui_effect.print_s))inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentin(* this is only necessary because I use on_change, which uses after-display.
In an action-handler, the actions would be scheduled on the same frame. *)Handle.recompute_view_until_stablehandle;[%expect{| hello |}];Bonsai.Var.setvar(Sexp.Atom"world");Handle.recompute_view_until_stablehandle;[%expect{| world |}];;let%expect_test"portal 2"=letcomponent=Bonsai_extra.with_inject_fixed_point(funinject_fix->let%substate1,inject1=Bonsai.state_machine1(moduleInt)(moduleInt)~default_model:0~apply_action:(fun~inject:_~schedule_eventinjectmodelaction->matchinjectwith|Activeinject->schedule_event(inject(model+action));action|Inactive->print_endline"inactive";model)inject_fixinlet%sub(),inject2=Bonsai.state_machine1(moduleUnit)(moduleInt)~default_model:()~apply_action:(fun~inject:_~schedule_eventstate1_modelaction->schedule_event(Ui_effect.print_s[%message(state1:intBonsai.Computation_status.t)(action:int)]);())state1inreturn@@Bonsai.Value.bothinject1inject2)inlethandle=Handle.create(modulestructtypet=int->unitEffect.ttypeincoming=intletview_=""letincoming=Fn.idend)componentinHandle.showhandle;[%expect{||}];Handle.do_actionshandle[1];Handle.flushhandle;[%expect{| ((state1 (Active 1)) (action 1)) |}];Handle.do_actionshandle[5];Handle.flushhandle;[%expect{| ((state1 (Active 5)) (action 6)) |}];Handle.do_actionshandle[10];Handle.flushhandle;[%expect{| ((state1 (Active 10)) (action 15)) |}];;let%expect_test"pipe"=letcomponent=let%subpush_and_pop=Bonsai_extra.pipe(moduleString)inreturn@@let%mappush,pop=push_and_popinletpops=let%bind.Bonsai.Effecta=popinUi_effect.print_s[%sexp"pop",(s:string),(a:string)]inpush,popinlethandle=Handle.create(modulestructtypet=(string->unitEffect.t)*(string->unitEffect.t)typeincoming=[`Pushofstring|`Popofstring]letview_=""letincoming(push,pop)=function|`Pushs->pushs|`Pops->pops;;end)componentinHandle.do_actionshandle[`Push"hello";`Pop"a"];Handle.flushhandle;[%expect{| (pop a hello) |}];Handle.do_actionshandle[`Push"world"];Handle.flushhandle;[%expect{| |}];Handle.do_actionshandle[`Pop"b"];Handle.flushhandle;[%expect{| (pop b world) |}];Handle.do_actionshandle[`Pop"c"];Handle.flushhandle;[%expect{| |}];Handle.do_actionshandle[`Push"foo"];Handle.flushhandle;[%expect{| (pop c foo) |}];Handle.do_actionshandle[`Push"hello";`Push"world";`Push"foo";`Pop"a";`Pop"b";`Pop"c"];Handle.flushhandle;[%expect{|
(pop a hello)
(pop b world)
(pop c foo) |}];;let%expect_test"multi-thunk"=letmoduleId=Core.Unique_id.Int()inletid=Bonsai.Expert.thunk(fun()->print_endline"pulling id!";Id.create())inletcomponent=let%map.Computationa=idandb=idinsprintf"%s %s"(Id.to_stringa)(Id.to_stringb)inlethandle=Handle.create(Result_spec.sexp(moduleString))componentinHandle.showhandle;[%expect{|
pulling id!
pulling id!
"1 0" |}];;let%expect_test"scope_model"=letvar=Bonsai.Var.createtrueinletcomponent=Bonsai.scope_model(moduleBool)~on:(Bonsai.Var.valuevar)(Bonsai.state(moduleString)~default_model:"default")inlethandle=Handle.create(modulestructtypet=string*(string->unitEffect.t)typeincoming=stringletview(s,_)=sletincoming(_,s)=send)componentinHandle.showhandle;[%expect{| default |}];Handle.do_actionshandle["a"];Handle.showhandle;[%expect{| a |}];Bonsai.Var.setvarfalse;Handle.showhandle;[%expect{| default |}];Handle.do_actionshandle["b"];Handle.showhandle;[%expect{| b |}];Bonsai.Var.setvartrue;Handle.showhandle;[%expect{| a |}];;let%expect_test"thunk-storage"=letmoduleId=Core.Unique_id.Int()inletvar=Bonsai.Var.createtrueinletid=Bonsai.Expert.thunk(fun()->print_endline"pulling id!";Id.create())inletcomponent=if%subBonsai.Var.valuevarthen(let%map.Computationid=idinId.to_stringid)elseBonsai.const""inlethandle=Handle.create(Result_spec.sexp(moduleString))componentinHandle.showhandle;[%expect{|
pulling id!
0 |}];Bonsai.Var.setvarfalse;Handle.showhandle;[%expect{| "" |}];Bonsai.Var.setvartrue;Handle.showhandle;[%expect{| 0 |}];;let%expect_test"action dropped in match%sub"=letcomponent=let%subx,set_x=Bonsai.state(moduleBool)~default_model:trueinmatch%subxwith|true->let%sub(),inject=Bonsai.state_machine1(moduleUnit)(moduleUnit)~default_model:()~apply_action:(fun~inject:_~schedule_event:_input()()->matchinputwith|Active()->print_endline"active"|Inactive->print_endline"inactive")(opaque_const_value())inBonsai.Edge.lifecycle~on_activate:(let%mapinject=injectandset_x=set_xinlet%bind.Effect()=set_xfalsein(* This call to [inject] below successfully schedules the effect,
but the effect never gets run because the effect that
just got executed switched which branch of the [match%sub] was
active, thus making it impossible to run the [apply_action]
function of the [state_machine1]. A similar component that uses
[state_machine0] would not have this problem. *)inject())()|false->Bonsai.const()inlethandle=Handle.create(Result_spec.sexp(moduleUnit))componentinHandle.showhandle;[%expect{| () |}];Handle.showhandle;[%expect{|
inactive
() |}];;let%test_module"mirror"=(modulestructletprepare_test~store~interactive=letstore=Bonsai.Var.createstoreinletinteractive=Bonsai.Var.createinteractiveinletstore_set=(funvalue->printf"store set to \"%s\""value;Bonsai.Var.setstorevalue)|>Ui_effect.of_sync_funinletinteractive_set=(funvalue->printf"interactive set to \"%s\""value;Bonsai.Var.setinteractivevalue)|>Ui_effect.of_sync_funinletcomponent=let%sub()=Bonsai_extra.mirror(moduleString)~store_set:(Bonsai.Value.returnstore_set)~interactive_set:(Bonsai.Value.returninteractive_set)~store_value:(Bonsai.Var.valuestore)~interactive_value:(Bonsai.Var.valueinteractive)inreturn(let%mapstore=Bonsai.Var.valuestoreandinteractive=Bonsai.Var.valueinteractiveinsprintf"store: %s, interactive: %s"storeinteractive)inlethandle=Handle.create(Result_spec.string(moduleString))componentinhandle,store,interactive;;let%expect_test"starts stable"=lethandle,_store,_interactive=prepare_test~store:"a"~interactive:"a"inHandle.showhandle;[%expect{| store: a, interactive: a |}];;let%expect_test"starts unstable"=lethandle,_store,_interactive=prepare_test~store:"a"~interactive:"b"inHandle.showhandle;[%expect{|
store: a, interactive: b
interactive set to "a" |}];Handle.showhandle;[%expect{| store: a, interactive: a |}];;let%expect_test"starts stable and then interactive changes"=lethandle,_store,interactive=prepare_test~store:"a"~interactive:"a"inHandle.showhandle;[%expect{| store: a, interactive: a |}];Bonsai.Var.setinteractive"b";Handle.showhandle;[%expect{|
store: a, interactive: b
store set to "b" |}];Handle.showhandle;[%expect{| store: b, interactive: b |}];;let%expect_test"starts stable and then store changes"=lethandle,store,_interactive=prepare_test~store:"a"~interactive:"a"inHandle.showhandle;[%expect{| store: a, interactive: a |}];Bonsai.Var.setstore"b";Handle.showhandle;[%expect{|
store: b, interactive: a
interactive set to "b" |}];Handle.showhandle;[%expect{| store: b, interactive: b |}];;let%expect_test"starts stable and then both change at the same time"=lethandle,store,interactive=prepare_test~store:"a"~interactive:"a"inHandle.showhandle;[%expect{| store: a, interactive: a |}];Bonsai.Var.setstore"b";Bonsai.Var.setinteractive"c";Handle.showhandle;[%expect{|
store: b, interactive: c
store set to "c" |}];Handle.showhandle;[%expect{| store: c, interactive: c |}];;end);;let%test_module"mirror'"=(modulestructletprepare_test~store~interactive=letstore=Bonsai.Var.createstoreinletinteractive=Bonsai.Var.createinteractiveinletstore_set=(funvalue->printf"store set to \"%s\""value;Bonsai.Var.setstore(Somevalue))|>Ui_effect.of_sync_funinletinteractive_set=(funvalue->printf"interactive set to \"%s\""value;Bonsai.Var.setinteractive(Somevalue))|>Ui_effect.of_sync_funinletcomponent=let%sub()=Bonsai_extra.mirror'(moduleString)~store_set:(Bonsai.Value.returnstore_set)~interactive_set:(Bonsai.Value.returninteractive_set)~store_value:(Bonsai.Var.valuestore)~interactive_value:(Bonsai.Var.valueinteractive)inlet%arrstore=Bonsai.Var.valuestoreandinteractive=Bonsai.Var.valueinteractiveinsprintf"store: %s, interactive: %s"(Option.valuestore~default:"<none>")(Option.valueinteractive~default:"<none>")inlethandle=Handle.create(Result_spec.string(moduleString))componentinhandle,store,interactive;;let%expect_test"starts both none"=lethandle,_store,_interactive=prepare_test~store:None~interactive:NoneinHandle.showhandle;[%expect{| store: <none>, interactive: <none> |}];;let%expect_test"starts interactive some"=lethandle,_store,_interactive=prepare_test~store:None~interactive:(Some"hi")inHandle.showhandle;[%expect{|
store: <none>, interactive: hi
store set to "hi" |}];Handle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts store some"=lethandle,_store,_interactive=prepare_test~store:(Some"hi")~interactive:NoneinHandle.showhandle;[%expect{|
store: hi, interactive: <none>
interactive set to "hi" |}];Handle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts both some (same value)"=lethandle,_store,_interactive=prepare_test~store:(Some"hi")~interactive:(Some"hi")inHandle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts both some (different values)"=lethandle,_store,_interactive=prepare_test~store:(Some"hi")~interactive:(Some"hello")inHandle.showhandle;[%expect{|
store: hi, interactive: hello
interactive set to "hi" |}];Handle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts both none, store set "=lethandle,store,_interactive=prepare_test~store:None~interactive:NoneinHandle.showhandle;[%expect{| store: <none>, interactive: <none> |}];Bonsai.Var.setstore(Some"hi");Handle.showhandle;[%expect{|
store: hi, interactive: <none>
interactive set to "hi" |}];Handle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts both none, interactive set "=lethandle,_store,interactive=prepare_test~store:None~interactive:NoneinHandle.showhandle;[%expect{| store: <none>, interactive: <none> |}];Bonsai.Var.setinteractive(Some"hi");Handle.showhandle;[%expect{|
store: <none>, interactive: hi
store set to "hi" |}];Handle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts both none, both set to same value"=lethandle,store,interactive=prepare_test~store:None~interactive:NoneinHandle.showhandle;[%expect{| store: <none>, interactive: <none> |}];Bonsai.Var.setinteractive(Some"hi");Bonsai.Var.setstore(Some"hi");Handle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts both none, both set to different values"=lethandle,store,interactive=prepare_test~store:None~interactive:NoneinHandle.showhandle;[%expect{| store: <none>, interactive: <none> |}];Bonsai.Var.setinteractive(Some"hi");Bonsai.Var.setstore(Some"hello");Handle.showhandle;[%expect{|
store: hello, interactive: hi
store set to "hi" |}];Handle.showhandle;[%expect{| store: hi, interactive: hi |}];;let%expect_test"starts both some, both set to different values"=lethandle,store,interactive=prepare_test~store:(Some"hi")~interactive:(Some"hi")inHandle.showhandle;[%expect{| store: hi, interactive: hi |}];Bonsai.Var.setinteractive(Some"abc");Bonsai.Var.setstore(Some"def");Handle.showhandle;[%expect{|
store: def, interactive: abc
store set to "abc" |}];Handle.showhandle;[%expect{| store: abc, interactive: abc |}];;let%expect_test"starts both some (same value), store reset to none"=lethandle,store,_interactive=prepare_test~store:(Some"hi")~interactive:(Some"hi")inHandle.showhandle;[%expect{| store: hi, interactive: hi |}];Bonsai.Var.setstoreNone;Handle.showhandle;(* The noneness isn't propagated to interactive *)[%expect{| store: <none>, interactive: hi |}];;let%expect_test"starts both some (same value), interactive reset to none"=lethandle,_store,interactive=prepare_test~store:(Some"hi")~interactive:(Some"hi")inHandle.showhandle;[%expect{| store: hi, interactive: hi |}];Bonsai.Var.setinteractiveNone;Handle.showhandle;(* The noneness isn't propagated to the store *)[%expect{| store: hi, interactive: <none> |}];;let%expect_test"starts both some (same value), both set to none"=lethandle,store,interactive=prepare_test~store:(Some"hi")~interactive:(Some"hi")inHandle.showhandle;[%expect{| store: hi, interactive: hi |}];Bonsai.Var.setstoreNone;Bonsai.Var.setinteractiveNone;Handle.showhandle;(* The noneness isn't propagated to the store *)[%expect{| store: <none>, interactive: <none> |}];;let%expect_test"starts both some (same value), interactive set to none, both swap"=lethandle,store,interactive=prepare_test~store:(Some"hi")~interactive:(Some"hi")inHandle.showhandle;[%expect{| store: hi, interactive: hi |}];Bonsai.Var.setstoreNone;Handle.showhandle;[%expect{| store: <none>, interactive: hi |}];Bonsai.Var.setstore(Some"abc");Bonsai.Var.setinteractiveNone;Handle.showhandle;[%expect{|
store: abc, interactive: <none>
interactive set to "abc" |}];Handle.showhandle;[%expect{| store: abc, interactive: abc |}];;let%expect_test"starts both some (same value), store set to none, both swap"=lethandle,store,interactive=prepare_test~store:(Some"hi")~interactive:(Some"hi")inHandle.showhandle;[%expect{| store: hi, interactive: hi |}];Bonsai.Var.setinteractiveNone;Handle.showhandle;[%expect{| store: hi, interactive: <none> |}];Bonsai.Var.setinteractive(Some"abc");Bonsai.Var.setstoreNone;Handle.showhandle;[%expect{|
store: <none>, interactive: abc
store set to "abc" |}];Handle.showhandle;[%expect{| store: abc, interactive: abc |}];;end);;let%expect_test"let%arr cutoff destruction"=letvar=Bonsai.Var.create(0,0)inletvalue=Bonsai.Var.valuevarinletcomponent=let%arra,_=valueinprint_endline"performing work!";ainlethandle=Handle.create(Result_spec.string(moduleInt))componentinHandle.showhandle;[%expect{|
performing work!
0 |}];Bonsai.Var.setvar(0,1);Handle.showhandle;(* No work is performed! *)[%expect{| 0 |}];Bonsai.Var.setvar(1,1);Handle.showhandle;[%expect{|
performing work!
1 |}];;let%test_module"regression"=(modulestruct(* The regression in question is caused by calling [Value.both] on a dynamic
[Value.Map] and a constant one. Instead of returning a [Value.Both] node, we'd
return a [Value.Fast_map], where the constant value is added to the tuple inside
the folded mapping function. However, when the mapping function that we're folding
into is used for getting better cutoff behavior, this "optimization" actually
undoes it by introducing a fresly-allocated tuple which will not cutoff correctly
anymore. *)moduleState=structtypet={a:int;b:int;c:int}[@@derivingfields]endlet%expect_test""=letstate_var=Bonsai.Var.create{State.a=2;b=3;c=4}inletstate=Bonsai.Var.valuestate_varinleta=Value.mapstate~f:State.ainletcomponentb=let%arra=aandb=binprintf"Recomputing ; a = %d\n"a;a+binletc=component(Value.mapstate~f:State.b)inlethandle=Handle.create(Result_spec.string(moduleInt))cinHandle.showhandle;[%expect{|
Recomputing ; a = 2
5 |}];Bonsai.Var.updatestate_var~f:(funstate->{statewithc=4});Handle.showhandle;[%expect{|
5 |}];;let%expect_test""=letstate_var=Bonsai.Var.create{State.a=2;b=3;c=4}inletstate=Bonsai.Var.valuestate_varinleta=Value.mapstate~f:State.ainletcomponentb=let%arra=aandb=binprintf"Recomputing ; a = %d\n"a;a+binletc=component(Value.return3)inlethandle=Handle.create(Result_spec.string(moduleInt))cinHandle.showhandle;[%expect{|
Recomputing ; a = 2
5 |}];Bonsai.Var.updatestate_var~f:(funstate->{statewithc=4});Handle.showhandle;[%expect{| 5 |}];;end);;let%expect_test"value_with_override"=letdefault_var=Bonsai.Var.create"First Model Value"inletvalue=Bonsai.Var.valuedefault_varinletcomponent=Bonsai_extra.value_with_override(moduleString)valueinlethandle=Handle.create(modulestructtypet=string*(string->unitEffect.t)typeincoming=stringletview(s,_)=sletincoming(_,s)=send)componentinHandle.showhandle;[%expect{| First Model Value |}];Bonsai.Var.setdefault_var"Second Model Value";Handle.showhandle;[%expect{| Second Model Value |}];Handle.do_actionshandle["First Override"];Handle.showhandle;[%expect{| First Override |}];Bonsai.Var.setdefault_var"Third Model Value";Handle.showhandle;(* Changes to the variable don't matter, now that we have an override. *)[%expect{| First Override |}];Handle.do_actionshandle["Second Override"];Handle.showhandle;[%expect{| Second Override |}];;let%expect_test"value_with_override in resetter"=letdefault_var=Bonsai.Var.create"First Model Value"inlethandle=letvalue=Bonsai.Var.valuedefault_varinletcomponent=Bonsai.with_model_resetter(Bonsai_extra.value_with_override(moduleString)value)inHandle.create(modulestructtypet=(string*(string->unitEffect.t))*unitEffect.ttypeincoming=[`Overrideofstring|`Reset]letview((s,_),_)=sletincoming((_,override),reset)action=matchactionwith|`Overrides->overrides|`Reset->reset;;end)componentinHandle.showhandle;[%expect{| First Model Value |}];Bonsai.Var.setdefault_var"Second Model Value";Handle.showhandle;[%expect{| Second Model Value |}];Handle.do_actionshandle[`Override"First Override"];Handle.showhandle;[%expect{| First Override |}];Bonsai.Var.setdefault_var"Third Model Value";Handle.showhandle;(* Changes to the variable don't matter, now that we have an override. *)[%expect{| First Override |}];Handle.do_actionshandle[`Reset];Handle.showhandle;(* Now, the change to the variable becomes visible. *)[%expect{| Third Model Value |}];(* But we can still override *)Handle.do_actionshandle[`Override"Second Override"];Handle.showhandle;[%expect{| Second Override |}];;let%expect_test"ordering behavior of skeleton traversal"=(* NOTE: This test just showcases current traversal order behavior in case it
were to change/matter in the future. *)letall_values=[Value.return();Bonsai.Var.value(Bonsai.Var.create());Value.cutoff(Value.return())~equal:phys_equal;Value.map(Value.both(Value.return())(Value.return()))~f:(fun((),())->())]|>List.reduce_exn~f:(Value.map2~f:(fun()()->()))inletc=let%subv=returnall_valuesinlet%subc1=Bonsai.state_machine1(moduleUnit)(moduleUnit)~default_model:()v~apply_action:(fun~inject:_~schedule_event:_(_:unitBonsai.Computation_status.t)()()->())inlet%subc2=Bonsai.state(moduleUnit)~default_model:()inlet%subc3=Bonsai.assoc(moduleUnit)(Value.returnUnit.Map.empty)~f:(fun__->Bonsai.const())inlet%subc4=match%subvwith|()->Bonsai.const()inlet%arr()=vand(),_=c1and(),_=c2and_=c3and()=c4in()inletskeleton=Bonsai.Private.Skeleton.Computation.of_computation(Bonsai.Private.reveal_computationc)inletpre_order_printer=objectinheritBonsai.Private.Skeleton.Traverse.mapassupermethod!valuevalue=printf"value - ";print_s[%message""~_:(Lazy.forcevalue.node_path:Bonsai.Private.Node_path.t)];super#valuevaluemethod!computationcomputation=printf"computation - ";print_s[%message""~_:(Lazy.forcecomputation.node_path:Bonsai.Private.Node_path.t)];super#computationcomputationendinpre_order_printer#computationskeleton|>(ignore:Bonsai.Private.Skeleton.Computation.t->unit);[%expect{|
computation - _1
computation - 1_1
value - 1_2
value - 1-1_1
value - 1-1-1_1
value - 1-1-1-1_1
value - 1-1-1-2_1
value - 1-1-2_1
value - 1-1-2-1_1
value - 1-2_1
value - 1-2-1_1
value - 1-2-1-1_1
value - 1-2-1-2_1
computation - 2_1
computation - 2-1_1
value - 2-1_2
computation - 2-2_1
computation - 2-2-1_1
computation - 2-2-2_1
computation - 2-2-2-1_1
value - 2-2-2-1-1_1
computation - 2-2-2-1-2_1
value - 2-2-2-1-2_2
computation - 2-2-2-2_1
computation - 2-2-2-2-1_1
computation - 2-2-2-2-1-1_1
value - 2-2-2-2-1-1_2
value - 2-2-2-2-1-1-1_1
computation - 2-2-2-2-1-2_1
value - 2-2-2-2-1-2_2
computation - 2-2-2-2-2_1
value - 2-2-2-2-2_2
value - 2-2-2-2-2-1_1
value - 2-2-2-2-2-1-1_1
value - 2-2-2-2-2-1-2_1
value - 2-2-2-2-2-1-2-1_1
value - 2-2-2-2-2-1-2-1-1_1
value - 2-2-2-2-2-1-2-2_1
value - 2-2-2-2-2-1-2-2-1_1
value - 2-2-2-2-2-1-2-2-1-1_1
value - 2-2-2-2-2-1-2-2-2_1
value - 2-2-2-2-2-1-2-2-2-1_1
value - 2-2-2-2-2-1-2-2-2-1-1_1
value - 2-2-2-2-2-1-2-2-2-2_1 |}];;let%expect_test"on_activate lifecycle events are run the second frame after the \
component becomes active"=letinput_var=Bonsai.Var.create()inletactive_var=Bonsai.Var.createtrueinletcomponent=let%sub(),inject=Bonsai.state_machine1(moduleUnit)(moduleUnit)~default_model:()~apply_action:(fun~inject:_~schedule_event:_(_:unitBonsai.Computation_status.t)()()->print_endline"on_activate")(Bonsai.Var.valueinput_var)inlet%subon_activate=let%arrinject=injectininject()inBonsai.Edge.lifecycle~on_activate()inlethandle=Handle.create(Result_spec.sexp(moduleUnit))(if%subBonsai.Var.valueactive_varthencomponentelsecomponent)in(* The on_activate does not run in the first frame; rather, it is enqueued in the effect
handler *)Handle.recompute_viewhandle;[%expect{| |}];(* Indeed, it does run the second frame *)Handle.recompute_viewhandle;[%expect{| on_activate |}];(* Flip the var to switch the active branch *)Bonsai.Var.setactive_varfalse;(* Once again, it's enqueued on the first frame, not run *)Handle.recompute_viewhandle;[%expect{| |}];(* But now, if the active branch flips, the on_activate action is dropped! *)Bonsai.Var.setactive_vartrue;Handle.recompute_viewhandle;[%expect{|
on_activate |}];;let%expect_test"State machine actions that are scheduled while running the actions for \
a frame are run on the same frame"=letcomponent=Bonsai.state_machine0(moduleUnit)(moduleInt)~default_model:()~apply_action:(fun~inject~schedule_event()n->print_s[%message(n:int)];ifn<=0then()elseschedule_event(inject(n-1)))inlethandle=Handle.create(modulestructtypet=unit*(int->unitEffect.t)typeincoming=intletview_=""letincoming((),inject)n=injectnend)componentin(* Schedules the action, but does not run it yet *)Handle.do_actionshandle[10];[%expect{| |}];(* Runs the action, which schedules more actions that all get run in the same frame *)Handle.flushhandle;[%expect{|
(n 10)
(n 9)
(n 8)
(n 7)
(n 6)
(n 5)
(n 4)
(n 3)
(n 2)
(n 1)
(n 0) |}];;let%expect_test"Bonsai.previous_value"=letinput_var=Bonsai.Var.create0inletactive_var=Bonsai.Var.createtrueinletcomponent=match%subBonsai.Var.valueactive_varwith|true->Bonsai.previous_value(moduleInt)(Bonsai.Var.valueinput_var)|false->Bonsai.constNoneinlethandle=Handle.create(Result_spec.sexp(modulestructtypet=intoption[@@derivingsexp,equal]end))componentinHandle.showhandle;[%expect{| () |}];Handle.showhandle;[%expect{| (0) |}];Bonsai.Var.setinput_var1;Handle.showhandle;[%expect{| (0) |}];Bonsai.Var.setinput_var2;Handle.showhandle;[%expect{| (1) |}];Handle.showhandle;[%expect{| (2) |}];Bonsai.Var.setactive_varfalse;Handle.showhandle;[%expect{| () |}];Bonsai.Var.setinput_var3;Handle.showhandle;[%expect{| () |}];Bonsai.Var.setactive_vartrue;Handle.showhandle;[%expect{| (2) |}];Handle.showhandle;[%expect{| (3) |}];;let%expect_test"most_recent_some"=letvar=Bonsai.Var.create1inletactive=Bonsai.Var.createtrueinletcomponent=match%subBonsai.Var.valueactivewith|true->Bonsai.most_recent_some(moduleInt)(Bonsai.Var.valuevar)~f:(funx->ifxmod2=0thenSomexelseNone)|false->Bonsai.constNoneinlethandle=Handle.create(Result_spec.sexp(modulestructtypet=intoption[@@derivingsexp]end))componentinHandle.showhandle;[%expect{| () |}];Bonsai.Var.setvar2;Handle.showhandle;[%expect{| (2) |}];Handle.showhandle;[%expect{| (2) |}];Bonsai.Var.setactivefalse;Handle.showhandle;[%expect{| () |}];Bonsai.Var.setactivetrue;Handle.showhandle;[%expect{| (2) |}];Bonsai.Var.setactivefalse;Handle.showhandle;[%expect{| () |}];Bonsai.Var.setvar6;Handle.showhandle;[%expect{| () |}];Bonsai.Var.setactivetrue;Handle.showhandle;[%expect{| (6) |}];;