From 6f735ef73d9bf02fc243d65bfb2d4835a5b1ab1c Mon Sep 17 00:00:00 2001 From: danielbinder <59804199+danielbinder@users.noreply.github.com> Date: Sat, 21 Oct 2023 13:40:45 +0200 Subject: [PATCH] Small imporvements Made progressMap independent of description, so multiple descriptions can be the same Added AtomicInteger instead, so indices are distinct Added descriptionMap for descriptions Added current index to each Progress object to identify it in progressMap Made description and list publicly available Added SafeVarargs annotation to varargs methods Inlined updateProgress method to directly access progressMap Used Thread.ofVirtual() so execution is suspended while printer thread sleeps Moved startTime inside the printer thread, so it only starts after the Progress object is actually created, not when Progress.reset() is called Removed description if size < 0 Added 0 to seconds if seconds are only single digit and minutes are present Updated Demo Updated README --- README.md | 12 ++++-- demo.png | Bin 13705 -> 13814 bytes src/Demo.java | 28 +++++++------ src/Progress.java | 98 +++++++++++++++++++++++++--------------------- 4 files changed, 77 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index e9ab8fa..358cc67 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Progress -This is a simple library for adding one or multiple console progress counter(s) to your project. +This is a simple library for adding one or multiple console progress counters to your project. ![demo.png](demo.png) ### I want to use and/or adapt this project @@ -12,8 +12,7 @@ Regardless of the license, it would be cool if you somehow mentioned, that you g 1) Add as a library (here shown in IntelliJ) or add the code directly to your project ![lib.png](lib.png) 2) Wrap the Object you want to track in `Progresss.of("optional description", someObj)` wherever you want to consume it e.g. - - You can use it anywhere, where you would use an iterator of a Collection or an Array, since it literally returns one - - The DESCRIPTIONS are used as a key to the progress map, SO THEY NEED TO BE UNIQUE + - You can use it anywhere, where you would use an `Iterator`, since the Iterator returned triggers the progress update - If you don't add a description, an incrementing counter will be added instead - ``` for(Thing t : Progress.of(thingList)) { @@ -21,11 +20,16 @@ Regardless of the license, it would be cool if you somehow mentioned, that you g } ``` 3) You can also register multiple Collections in multiple Threads -4) If you want to reset the counter use `Progress.reset()` +4) If you want to reset the counter use `Progress.reset()` (as late as possible) 5) You can find more details in the [Demo.java](https://github.com/danielbinder/Progress/blob/master/src/Demo.java) ### Implementation details - Update rate of the counter is hard coded to 100ms. +- The progress amount itself is not threadsafe, since syncing the numbers would unnecessarily take additional time, **since** + - either something is very fast, and you + - don't really need a progress counter or + - it's too fast for you to notice the slightly wrong numbers + - or it's slow enough that the progress is basically up2date after 100ms - The goal of complicated progress update loop is to reduce IO access as much as possible. - The progress will be shown as a percentage (int) - Time is `elapsed time | estimated time left` diff --git a/demo.png b/demo.png index c043c3cab874334ec57efc8e978289d30f084c5b..34b9f66bce803cbb11186a921429d7477dc70782 100644 GIT binary patch literal 13814 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

;3JR zHwuC!22BNd4TWpro)5iOxwE?|#~Gt!HF8YS1EJma^&+*yNM@5F%X-JCPDXdw@^7g^8Sb9Z0%2eCdP2XK% zdjD9V{kFyTxquw|9gQJ6Q(sd@5TD58ys;rs!kU21u;SSl+YbUmfB-rZGHW-1*JI^` zY-K|J^#efR#qp*Nlyh(f2NWqm|Ake|8%(HCy|$vy5Q%3sLg5~{j8%DcMCzn|rMgwt zllXazKOOHn%dp6RjJ@4M%jk+w{HsH^k27|2|09m;N}7p>ITiIWO1%OJ0`r-%yQd{^ zgY|2L*tdyK7E=1+cRBiDG;&8mn{R?y+fYi|YAieLgJ+fN9>K!%rCeS;arg|z*kc>#cq>t4^i>hpCByGmlG z7>~VpY4Sb6`^bdV!Rx~v`O!Pg?amJkQbRTgZ7YN6@on{BHuq$u^0Wi?OQ`)J zrQ-BRNyfw|B#pP7dW3P2iP%(t%{`ZdBo>|s8_`2~a{cK`>^_(MrKpr+FU?X15&?(m z79~n6QdPjRD=>D>n2?!&$No^}+I-<*z6#jSr||@?n?wKbf|x(?ntF}tSQM8SoGqK= zvg37TtG@d%_F(^BGDj%khMdYqZ@Ck?kTStE0Bw?leT zWRxE)>)&bml!{bOu=}LeL^c5#D+o2~`*iD7Z$2J$I3vzG&jP+{5XMZ)|Ft?ra`^)3 z_%JizEqAE!!McZYT+}o28ifJ%*AI_XfRd{jGWUxq?@w=JW>3Mt;&r zxO^S!ygC@!1w%nyX`e|nS+_alQeyOkT-Hba7)?F=33*$m3jq`u)(n@O8fOJx37dm3;nCfO_nF@U)B5~w&aF&nWe zKQ3h)66_xbIdlOC_PaukgUVT@10kh+Y`Uw_rG*pF@}8IqZFA`wH2-lkS)7;>|EetP z0`9NhZOE#&YvYSfkX_CGL9bvC%L&vA`6A|{)cwyjOiBu~X9zHE30uYV@xQNP`VTr6 d5C1~WnflJZ`n1t%yP3{{nm;Bg6mz literal 13705 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/s*cX`7^?Rj%zmA*m? z`yA;Rw2_>7*?E__a44xjEu_B(-ay0SslLGD(RothBB>Y_mtv_y(+fN#Xz52JW46^m zys;AZb{H|d*fi?>MLwu^{60~M9de0MzPx%#IQArA5+Sr)YGV1PFmgp0;}sN=8>DW= z5ml0R5s~+3JPF2v2NeY}<@`~vkZ7p2fx&*4lnOnwz50G;(^~SN>)1Lizt+zZDR$OR z2rd$lVO~2&H=>)Bn8Cs*F^G8 z<0(r>b+@cajB7No-#f`cVi*aTV7<9uA}*P+aU_&&eoF2#dzIMXO*n7pZQF@Gj=aP^ z5>f*o zZ++((gj6vdDK_K0>g!rt9`A^5(KZ2Q?`6fSjWEaEtNJH{XUUCf>$)^L)G0C zkmjJ$A^v2*;QfBLmso3!;E3tn6Szu^b{#Z5F>6epBpcCZK|!q`rA588q8j-9?j%7w z>5bs_q;H5qYdP42`g&Us>V8M)9r||7sc+*rDA6z(uzS)KKz49q&)2T$Vo`vb*svI;XZFI)~mBWVE2IZhe+Dpcu>#Q>1$CM@6pC*2=2$ zw0HUOD67~8em)t_DCNT>(F}u1wzCavM2MOntmjZ>$6J8i^uL(~k{qWN%p|F8w`;B7 znXyW0^Jp4cg~cYewF}5SF>!$qe8~IIs$cK+T~JV`=V6atkrG1?F%jUf`swEGy0GQSDGbGtQmKxTEl?v+wV-VsO;y^2j= z9(6{?4;jeyGCT9m#b5MxTyfgl=|F;h(*tZl&Dh$d4MOvV1y7N;8>DA53!XL=P4&@6 zRkko6T`OveG8lS3hjh10V8LdcEuhS}(MOH;fTwcnQmcD<&*wJ-bm@$5KJ?aaXBXHO zIt;b#0;%y%%`;vrvC~VduAwn4NI4bq7c;d!$3Io(^oed_QjIp%(V6gN#%t%!pg<}R zemjhAfYqW8wUAv1!zeXIH|RlR?&VRdSmzpe+4x9k{dKPTD0Da#tl`pzExmNMNW;#D za2gr7!3{3i39}tQ=t~D-aNikR%=C^l!IKrpz&K7l)&G<2ljN?hgzaLlr}j>K7xXQ0 z`=`asjw#!Xa|Y!hZDIafD&fgA3u%jrs``M?Q2#|fcP&2q+0kXHUHC<{rGJW2F{h63~_t-&E4&Ph+2a zOY1J_Mye%GR~0dc6IKk1Gb^jSOYy*0>xcB~_Io;v%6zaY_*SB58XX+-8*eW&Rsw@% z-h-YPthQr*LJsk5T6#;gpXQUNp`$V%2rh|A2e4BhCOz@G!QYT0bsW+=zM3|xy?wsT zI%T7*Uocw{6c!ddN$QtxOW5zo&aO%3-|77e+OrqmG9e%ChToj1zwYDe_b&P>G?iBC z4M29&_!|1fy@2IrzLDjog#_K0>@{%kr4?%-1oJTv9f!$Wmb_*I4AY>kR-M0~-}x!X z%Q5&_op?Ni$Mk@9_~t;>1f7=>pYdZ26XMsJ4rA=k*5Eb+-L_} znPeMJScV!^CW4SI=F6dAh9^m$&`@f(92tZLZ~s$Ht=Ds@!G_dBpi1k)_`;+ymkrfn z$|rJ9-&rx=xg%dHWI&6COJ<)j?>e=(nwWp3s#j?1JkW}LuIe$-XSsz(LzFk>kg3bV zB6Qpd?}80}FVC+hU0=sEUuXv44|82!FlEU&btST0q&yNGOi+U{Nb_=%IPhZai}$7O4=}BI zsc9J#2n&#nh;yMpjozhjvpdEX&Y-{9wT896HKXFpRbOn0pLrt~l!(K-c zjFlz}k)ZIMaYs6-M#?caqTRNFI~jPAix1s$JjMW-LuK5p4}jj!%s@#@oJs*nZ_8sd zV)f%DP@^&q%PHhXwwGXGa|8du_D$pkkv4cTE@Q$yJ9nIV4zXNmNX>C zg15lL%%;)DbkxF?j%qS7e@|(b(U^1V7W|b`LY8F2P?~Mas#R|rUQA*%*hV~>Up=or zl8Y!QN9kuFq9pv&_q>Z?vCE)eg8gSPy&PV6<$2g&zW2zXy3ZmoaJENMoamvxF1?c+ z#~v8e!EjcYhVS@yJ%D+cX(Icmos5-*D~1hvYvmMr!A|fH1u!^LTJL81I==B$&)EtZ z`4lK9E~H)>RGWb?qb-_xN(T%dGCpTPzsNO;9M+1yn9*g zC(;ox>;kFupAXN@NJw)l>nN$Hy7b%6m48zUc=qcZ9cUV2*e{P~FF>kL2nhcq7t1TK z26a2)0a_%kDL41_oZ~pjqmwg0IbMym0W2Bzqn&g{tx&JQbcf0L!xNgZ{qnkFZsp|A z6hShZR*gOxiL+>Fmtk>$L9n2YxvWV~F6c0JuroMn7A*egwKeXu+i@Zbcgi{LUtoxi zH>sToyHxm&P3lNqgTqgF=LUmOmawQhrjMW9>#9%uvVgEewLt>zg}nhkU2U=sF12ha zA>dUV-Av4}Y0HKsF~yDub&1K@%k9R|E4umV*;kp`ByIcYaPpE}JM)2NdRJeL`19Oe zk0U@WN$oJn&t$lxS}e==VCyZ(Cfkz93)6)LItj($mTCnr-6`F^lzJ03FF)bbJa z!CHv?s1?_M%&add6FlM>1&^Z($?kUk#W{tJ33>Jy7*f>_Z?(d&hi&Q=T$U5WJ1y+T$d+J%oDD}O4_oG{DaB3wEZmO6ti@}46%CcSLW%EiUQl5Vi z?`C1k`0pKZU?|^rd}2OQ4EZ33^q}T=_ZD> z=ih7pZOF;+kjnw0KuFXu!ISMX%rSw{d zKBXvfXh5G{$V~2Jwa+{ZKzW-}(tNXugwhcSYa%OnyM+0(quTcAw2gYlF?#y<2Ct&! zJ0zWl3paiqV$fq5jM7C$q~C<7HlPOif=MeX0F%E%TK+8YNd&nK79Lw#tgxP}S1BwW zhQnyDln}7FbnWhZ`0eHQqcwqUEkb7rJhQtz2Jd`PY}*}S`I;6Si07siO{L+B^D!we zz>WY~6f!YLt7b24U+K1s>rv%h&2#Sx{~-ZIqiAHwOArk!1gKsh&*_lHAcVI5T< zJd2quBvR!N&)A}&o->>&{}aGgmSY3S-)30H?MW(ry2^=4l}XR<*HFCu$v9hm{+!JC z;-ue~5$vn!=jUmFDSs#LYz#wLV;-+N-gzg@FKYCc>uQmqFPf?op(y}i=L2rZ55&u0 z&cM;6S4{^veM(6Q*e<%U&)$GYowG^!sRXFEBfdN5^Q~)O$+@FPI$e+OX0h4q)J|_+ z`i><|mu9A&$yjd9R;bHpp@_s%E>^RjSny!J$nVwo3fW%s=b~69JurQnAqNLQ0Ta;C zqW*R)X}w&B+y>P3Q`-dkS5ME{8oxzo&!m?tWV zcag@a?}deik8HSiVpaV*>%G*lZBssalWTKs(&@vIKB5z|uEn`65C)kz#NRuSLA+C^ zB;e(h`_~OoXgGVn?49&s>@)n`>fZPxG<3L}1WkxW^A8Qj1u)QN^}7N*l%Q@{`##$4 zE`=2!#u1!T;Tc|ar)cw)dE>EtuU*aX+lO!;c|&C6Ukp_9x9lg&fj3)my1n)Wr(*rz zdjB^RH1vk2#O%3a0b$`l{s&1(dj$yr!#IV*IFAG@HT)_OXWY>fJk;}0X37+7e6p+- z^|0?%%z_wMOoFS9F8e13A)7@ylJ~t-+xNIt4e`E`-DDyDIz-_+0cEuW^XFrelpikc zhucXN9G?44L2hTa=`B>H1w2fY*pN9cs@qA>Dmf|LyvxYSbT&@EYlV9|)>o3aykCNALSjwYyUBj`#;$TNPU3 z8Sl;1hn$W`pP~GXCyna8K?b2U``^mvUY_pmP$-MUCE&ZmT;~1kNCXx=l9Z+thU!t|G(?yJO&Za@NP!;vbxQP?{%E8CSg=Z& zAZU#C4Yw=EwpY9GF7}gp%XGpKwi=6!L?_J2mrk~kA_C^1My6>Ql^1k862fyI3~P4l?M(nrT&g=lp-nUT!Ao>QIk44#XG2}gUI@#2R?(v07n)|* z6D8EUrIapCz)jTj+DDcG?1>4`LfZXyV{+B3kiu#LHhO2>Yfy4BUuqRPl#k1W6HkQ1 zl#cWDPIWvrC9;_s8LqMMWe;8HwyuEeU_rrr{1xR{DjkR4h+}8cx;HbPKVPmBF|Yl( z=1Wua(X%vTjA>0>=0HqV8?s|j!PZN(cEY6t`$zXJ3}j$O<1P|ExaRCp!JUP6ndscN_iOQpT$AQnyZ%G%)$YX644 zL*YI?G13zlN&YZ--rFX%uq7@t)}YsLP9BZHPU^1A@x9QL%t{`k^k%_e8djX*RaZa-(}*6B?42yvIkwqw z(Yo<Q&Qa?S68sh_&N=r$vbV>LsN}A+VfRbup2a>G|8p4~oh7X5H7o|@q z0EO=8kpDcz6M9$LPkA0r{Z;;@aPnFHhq5y-f!rHd<0L3_B_n8X?nLPSl&77r{=Gac zJMbU!bUteu%v?zJ?)3h}5BHe#&lpUQlC-Puopg!+B}~)UYaXj8ZT{(u`u=!gp`aqbcpacA}V7NZ&jWFr3tpo;beJEH7K0IHsxVl>uBEiT(ru#Mbjk9Z&bp zRIFYAjp~ND)v`KcK0>p}iw6RP4SCBAp>=mYODM}5lw;heOb$0}XTbWiiivsUJI$e% zqLS%*vg2471DiUppCx7+0%t#+@*=JBKIbpuuYtx{>Ht6A9ZgGlkOEk zAECoHcclGg!ZI*iIqYVt#hE`3-K<~zEp@lqRsG8p4jpr{ zxNscL+t*LGgk8UPDzwLZ@~WyPe{oxiWm}<|Nr9Fd&p(kioY~Y_+j&wfOT~MKOT))= z-sDPC_;5LaGyv(8yAcXa`?ym z%b&8;l0X&;WsN@>7FSD9jZpaCMF6!s+f1>Dm1HzAB-eM(`nO);!E!#x_nkuW(0t^x zSiPjHqSR1XIJ$zyNeGNvzwwqYfok%pW!VB8`pzgnNW^^CB~I9K9evHq2*S>j5(@+J zf+j|Tqz+WFm@>vtp$e$-g~H<|B>2&32<2<>0f}Z9?ZLVEZ#1v@Zx`SqLy=*8B6oA_ zA_BJ#BE*))-cCz?v?XF&3T2{FjW_Qf6-0$S5&L>WE3tTdex z!u0{^bTTnX0*Wjyh7-qBYrDi}%-7SRq_2bc&oP}C2lDdm1D9D#h*b!gyN^>oCna-4 z(?hp8^$3z_>a_pmHB4qO%oQiXFg8p&P9Rz>(5sKCW&6wyRZK?yq+eWJwtX=R9Lf;t zd)^E=QEy?s{oaVms(*DFY;{JwJ+cXX;Rnq2`A~;x>h$3qd@yVKDC@zFH&xS^q{sEO2sDK1kK?^c9IEivG7 zOzPY9DOiljd6rzc7ZUR`IoQ_mI5@j2EEiNY_4ATq-ZLrYQyWr0DaLd*cy3A}o5Hm0 zV(L^udNuv(XgyE9x64{z0$zw0qE70T0cje@PrXlT(ha4DAWjl@_;7MAdw0)lTgvCC z&Q5;LlXAlKLFx2{cp@~hszyKD527_S@m4%YligX*f6vCgyBhlTq}b2<9L+Bbdc?0D zy++DkIq12%-Rz0IaRy{*A{?CE=u3dmCaAEPHo%K=CRhP>0hYQf~ z9hn!r%z8{ss=<`xrSgJ=AWgo@1^0#U4@^d)(!9Akl^6Vk*t>7BS(2odZqSXbg;n}5 zY6qK=0W0~g7c+BS*Mfc#b9}!lEX%U9JlRca2)Wqc+>S4NMxSlm#TgnVL{*eu#bxmq zkIT)0zQ>fBX4pVX`{78*%Bf{%Rau_vQf~6ukbex1Y=V-cQMXZsTHbmo4E8gSot)!8 z=7`#?QvH(K-eFy%G)v&d-d3rfi(GZ&7z<9(hE_wieaz#i4LZdx1e^SAT{?ge56 z`InYP#-=&3RT_)e+_sq%6vLjsz##DKrKg^iCh`kRu^_rqvAdMe*oC$3HMsCEE>zA% zpBr*a+`6*_NDB$JIR?p} zY%cpk3G#BcFYvcxG9oS=`XX6K_Zb2EK)47dY(ym5i5@62WT-^>UBu*Q%+uQ@DwaM_jiz9`Ku+D*b@pu?U)g1lLm4 zI@?U@@L5u0ah2nqzl9!7Sy7p7Xz3>a)M2dK%?cbpW>2nV6n-fDN1PfTE1QE=`tG06_fLL;ouKcVk=4Pg@azyn&>V%i4;RPcU~i&aon z)k$IGpztfY?AQ9zt@eu|Dlwafzvz*8CprX6wT)c`4=Dp8G3%HHHMFW=Waf?;Z7SI* z?|h?otVQ+0>u58yZeiV-QE*ztgHhMlEYeOnE0b@RrFmL|mH}$C)S67sk<4)qd=`QW zka20ve!DCckJ2R12hlW@y;8}l-VJC8NXy$ynn-dRcoNj4+rT!w%sh}t9>EWdT#8i= zb+O4H6&Bn3)_tkus-6O!Rss(mYX#99u(v*@-2KVTZ)b7ueoXQ$2IE!hDgTgs7 zF`3|h>Lk`0rNlORmceB{>gSTmx))tY+?loEvF8$`DCU49su$+?3NC@LK>P4AiMGp% zW~C96oBz80CB|NLX0{!s&0@)VBm66%~x?fyW&rvlFi3-CrpFjJU{za0d9PPOgV*sY05vP=j z&C~n6=RMeJeNw*ZkEeYGY>3TD?igc~6*pZ`Qbs~RZdho_4T&{bVb9MvXen=^_8@of zhqA?j`cVk%c7SKYz($WcVQXVFkBM4 z5GU@SxX=X*y5jh}QS=LmA~f*)NH#dwx;?fh9b3nI$aS&{WT-)2Na+W>ug9vGcZCdx z5e%45h$8c11yUAu@kdl*R~x(ENvI5FeCl!${gB5H=dgFm=b05;)FIJ~sp?f%R2Bm= zMlg2>B0o@+ZpJ+k#OUmjeH2Tx-fs=E?ba z`~H>->}_yldV&3zp*LAu?H6jH9@i#-L6gH19lh*Qhw8mGSgC>Ub9U;`lVN<9L^|QE z$f`mzEySpO8KNUo^_=zV{v2}4t_@GGevwwJ7H?PfuGLEgH^m9Er1nD^WMajP-9+}I ztJYi$ZqX6~wFKfB{G=H(PLJA|1Ov87*~sx%PI+6{30PSv%xHDc+2!3SOTpgxP`jCy zxWOZCTHaqKGzpDNVA#x2l|GACAlOJAni!5#tyWj1eysF*iYacIvIy$wJIJ^oXbE9mc@EE zbg#9Ju2XjDI1bpSko8(q5D$11iPd_oYCYCmQMq&7P46bns$S^qRlw+<1DhhR(;n(Y z{77fy=9^!^7S!SAu6q}{rYpunx~8Ei_~%nle%=L_t4&RywKk+#qJf)rf2)!7ZW#L< zrPGdJ8Z5;fQ;l(CoZn||8kXz<0CVlOv;UPeTUZbSIbj2Vmw&AqHZuTOi;$0A8jJLc zcCTwHKTmG>$3!zqF=De&sm4)=z?&tgZ2NR%EC#{DGrfWEhl^1E59yNl`XevoO{fix z^k2tk4la=pGllim$xqQhjbnx~X(KAmV;#i0*Ph#2{xlo0;?yWdAwvE5y-L2i>eM@H zZ87ze+w7*HN005Qq*J+_h#Qz~9#?VVc5L$QT7yQ{T(M^>x>8mVqwKA|qh-+m7#b#~1NZn`XPj|G$B=}J7!klZdy zbfJu~_J)-3j|`DWt2Z(EEaF*Z3acyHRyIz7zqbV?F!LGninADpUn*|!MnEK0fx5pw zu-=eq>kQd=gTi5EpGS9|xSo)aPVDHQCvo^rBE9u+%` zuOGBAM2atXog_@d@6;_noLOjIDuDHAWl+P&l<)A&VPPSe)*?#{EhN%iu}B&-Ovx_? zo&3C3MiqvamO7#S@#h(5WytNaHP1Jhw5pH(aUPFjaU!VS@+iu+%a!4sv)QbgkZ-*d z{QEF~U2-gC*8rEhz+xqv?bVkKr<07h2b%*1wFwBfdTCJ|ts6KD1r+%$!}=$nv5{Wm zJ^XO!4Mrh`7dfrN>NPKSuFunrbME&uiu(z0*vpKR{$SNUlK2xlh|EhAq~Dk>ibxFY zd8f9W;(}kUNguo!c3Bc9&wC4RcwBtgaOr^@q#-+(7(n;dB zKf6j9Hh+%hAqrto+91CFlKUuEVpgY~GhI(oBmThTy?UX9w(^dZBp zxHq>Mkg_AF-z(boczEcFYyjKn6g7z^d9OsGpwDI2V73-&Yw8y9UK8=i>C1NQi!$61 zb&j=8nX?RsoZ5n2`GrG2g^}VHS%~QHjU^0toZ$u2*)=24%*IN~)?u5aIwy?@dP5{% z@@Ff5ka@;%Y+&!uUr9>WcPAB#_s)2P7Aq-y;aM5`qo2;Bg&az5lgH=n;B70X&$IH1XodViCXg8=HHz-Q9+?^TWbME# zJrv%(&hoo``smeZIdZPX_dMNJ|Ga-Qo{(M&kqhD503V>;YglAif-b%!T!QIhhrA2> zUa$4Q*p8iRFxCp0R6Bo}@TgDd0JQF0H>Z!wXe`VD>)a7lhnW;zI!x355El!qS+$8@ zaXW*@(18>l1gA%keyzN2NC%_W$15!IboYH~_++qW2uotJK@6%%uLpKLxAz$powH6Y z*JPK=!QGketi7TW!3=zmSodGptbGVKEYBJXDqF9wzmENUH3AfEAb1X9EX5lZ5XU6` zwS${~>AG3C7bj-*sO|Z(dvo`SN*JcZf!)49Hv>KJ`CZ4ZKPGR$yVeNW#lP&)hD~s1 zhiy95-H^%JSTi394%;L0$~!v;U1Xet%pSl!!eEQp*9l=hx&#niOB3uSS%p5)XvvE@Z1g{sXhHJn`wL88m|$NdQ3|1Q}G=NNMT^Fm|pN!lG9+Fu)%^j|LO lT;RW7ZS)_DkHFr5K6lMxLX6lSi><)GBt&F|E5GUc|1U7avcLcU diff --git a/src/Demo.java b/src/Demo.java index 766cd56..72b15d8 100644 --- a/src/Demo.java +++ b/src/Demo.java @@ -3,19 +3,21 @@ import java.util.stream.IntStream; public class Demo { - public void count() { - Random r = new Random(); + private static final int MAX_SLEEP_MS = 1000; + private static final int MAX_ELEMENTS_IN_LIST = 1234; + private static final Random r = new Random(); + - // Simulate real progress + public void count() { try { - // Sleep up to 1s - Thread.sleep(r.nextInt(1, 1000)); + // Simulate real work by sleeping + Thread.sleep(r.nextInt(1, MAX_SLEEP_MS)); } catch(InterruptedException ignored) {} } public static void main(String[] args) { System.out.println("Single counter demo:"); - List demos = IntStream.range(0, 100) + List demos = IntStream.range(0, 123) .mapToObj(i -> new Demo()) .toList(); @@ -29,13 +31,15 @@ public static void main(String[] args) { System.out.println("\n\nMulticounter demo:"); Progress.reset(); IntStream.range(0, 5) - .mapToObj(l -> IntStream.range(0, 100) + // Random amount of list elements + .mapToObj(l -> IntStream.range(0, r.nextInt(MAX_ELEMENTS_IN_LIST)) .mapToObj(i -> new Demo()) .toList()) - .parallel() - // Register multiple lists - .forEach(list -> { - for(Demo d : Progress.of(list)) d.count(); - }); + // Register multiple counters in different threads + .forEach(list -> + Thread.ofPlatform().start(() -> { + for(Demo d : Progress.of(list)) d.count(); + }) + ); } } diff --git a/src/Progress.java b/src/Progress.java index 18fb5c5..f55e740 100644 --- a/src/Progress.java +++ b/src/Progress.java @@ -1,30 +1,43 @@ import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class Progress implements Iterable { - private static final Map progressMap = new HashMap<>(); - private static int i = 0; - private static long startTime; - private final String description; - private final List list; + private static final Map progressMap = new HashMap<>(); + private static final Map descriptionMap = new HashMap<>(); + private static final AtomicInteger i = new AtomicInteger(); + private final int counterIndex; + public final String description; + public final List list; static { reset(); } private Progress(String description, List list) { - progressMap.put(description, 0); + counterIndex = i.getAndIncrement(); + progressMap.put(counterIndex, 0); + descriptionMap.put(counterIndex, description); this.description = description; this.list = list; } + private Progress(List list) { + counterIndex = i.getAndIncrement(); + progressMap.put(counterIndex, 0); + + description = String.valueOf(counterIndex); + descriptionMap.put(counterIndex, description); + this.list = list; + } + public static Progress of(String description, List list) { return new Progress<>(description, list); } public static Progress of(List list) { - return of(String.valueOf(i++), list); + return new Progress<>(list); } public static Progress of(String description, Collection collection) { @@ -35,10 +48,12 @@ public static Progress of(Collection collection) { return of(collection.stream().toList()); } + @SafeVarargs public static Progress of(String description, T...array) { return of(description, Arrays.asList(array)); } + @SafeVarargs public static Progress of(T...array) { return of(Arrays.asList(array)); } @@ -55,62 +70,55 @@ public boolean hasNext() { @Override public T next() { - updateProgress(description, (100 * i) / Math.max(1, list.size() - 1)); + progressMap.put(counterIndex, (100 * i) / Math.max(1, list.size() - 1)); return list.get(i++); } }; } - private static void updateProgress(String description, int percentage) { - progressMap.put(description, percentage); - } - public static void reset() { progressMap.clear(); - i = 0; - startTime = System.currentTimeMillis(); - new Thread() { - @Override - public void run() { - super.run(); + i.set(1); - while(progressMap.isEmpty()) { - try { - Thread.sleep(100); - } catch(InterruptedException ignored) {} - } + Thread.ofVirtual().start(() -> { + while(progressMap.isEmpty()) { + try { + Thread.sleep(100); + } catch(InterruptedException ignored) {} + } + + long startTime = System.currentTimeMillis(); + + String oldString = ""; + int minProgress = progressMap.values().stream() + .min(Integer::compareTo) + // stop if no minimum found + .orElse(100); - String oldString = ""; - int minProgress = progressMap.values().stream() + while(minProgress < 100) { + minProgress = progressMap.values().stream() .min(Integer::compareTo) // stop if no minimum found .orElse(100); - while(minProgress < 100) { - minProgress = progressMap.values().stream() - .min(Integer::compareTo) - // stop if no minimum found - .orElse(100); + String currString = "\u001B[32m" + progressMap.keySet().stream() + .map(i -> (progressMap.size() > 1 || !descriptionMap.get(i).equals("1") ? "[" + descriptionMap.get(i) + "] " : "") + progressMap.get(i) + "% ") + .collect(Collectors.joining("", "", getTimeString(startTime, minProgress))); - String currString = "\u001B[32m" + progressMap.keySet().stream() - .map(d -> "[" + d + "] " + progressMap.get(d) + "% ") - .collect(Collectors.joining("", "", getTimeString(startTime, minProgress))); + if(!currString.equals(oldString)) { + System.out.print("\b".repeat(oldString.length())); - if(!currString.equals(oldString)) { - System.out.print("\b".repeat(oldString.length())); + oldString = currString; - oldString = currString; - - System.out.print(currString); - } - - try { - Thread.sleep(100); - } catch(InterruptedException ignored) {} + System.out.print(currString); } + + try { + Thread.sleep(100); + } catch(InterruptedException ignored) {} } - }.start(); + }); } private static String getTimeString(long startTime, int minProgress) { @@ -135,6 +143,6 @@ private static String formatTimeMillis(long timeMillis) { (d == 0 ? "" : (y > 0 ? "0".repeat(3 - String.valueOf(d).length()) + d : d) + "d") + (h == 0 ? "" : (d > 0 ? "0".repeat(2 - String.valueOf(h).length()) + h : h) + ":") + (min == 0 ? "" : (h > 0 ? "0".repeat(2 - String.valueOf(min).length()) + min : min) + ":") + - (h == 0 && min == 0 ? s + "s" : s); + (h == 0 && min == 0 ? s + "s" : (s < 10 ? "0" + s : s)); } }