@@ -794,16 +794,327 @@ function finishTest() {
794794 document . body . appendChild ( epilogue ) ;
795795}
796796
797- /// Prefer `call(() => { ... })` to `(() => { ... })()`\
798- /// This way, it's clear up-front that we're calling not just defining.
797+ // -
798+
799+ /**
800+ * `=> { return fn(); }`
801+ *
802+ * To be clear up front that we're calling (instead of defining):
803+ * ```
804+ * call(() => {
805+ * ...
806+ * });
807+ * ```
808+ *
809+ * As opposed to:
810+ * ```
811+ * (() => {
812+ * ...
813+ * })();
814+ * ```
815+ *
816+ * @param {function(): any } fn
817+ */
799818function call ( fn ) {
800819 return fn ( ) ;
801820}
802821
803- /// `for (const i of range(3))` => 0, 1, 2
804- /// Don't use `for...in range(n)`, it will not work.
805- function * range ( n ) {
806- for ( let i = 0 ; i < n ; i ++ ) {
807- yield i ;
822+ // -
823+
824+ /**
825+ * A la python:
826+ * * range(3) => [0,1,2]
827+ * * range(1,3) => [1,2]
828+ * @param {number } a
829+ * @param {number } [b]
830+ * @returns {number[] } [min(a,b), ... , max(a,b)-1]
831+ */
832+ function range ( a , b ) {
833+ b = b || 0 ;
834+ const begin = Math . min ( a , b ) ;
835+ const end = Math . max ( a , b ) ;
836+ return new Array ( end - begin ) . fill ( ) . map ( ( _ , i ) => begin + i ) ;
837+ }
838+ {
839+ let was ;
840+ console . assert ( ( was = range ( 0 ) ) . toString ( ) == [ ] . toString ( ) , { was} ) ;
841+ console . assert ( ( was = range ( 1 ) ) . toString ( ) == [ 0 ] . toString ( ) , { was} ) ;
842+ console . assert ( ( was = range ( 3 ) ) . toString ( ) == [ 0 , 1 , 2 ] . toString ( ) , { was} ) ;
843+ console . assert ( ( was = range ( 1 , 3 ) ) . toString ( ) == [ 1 , 2 ] . toString ( ) , { was} ) ;
844+ }
845+
846+ // -
847+
848+ /**
849+ * `=> { throw v; }`
850+ *
851+ * Like `throw`, but usable as an expression not just a statement.\
852+ * E.g. `let found = foo.bar || throwv({foo, msg: 'foo must have .bar!'});`
853+ * @param {any } v
854+ * @throws Always throws `v`!
855+ */
856+ function throwv ( v ) {
857+ throw v ;
858+ }
859+
860+ // -
861+
862+ /**
863+ * @typedef {object } ConfigDict
864+ * @property {any= } key
865+ */
866+
867+ /**
868+ * @typedef {ConfigDict[] } ConfigDictList
869+ */
870+
871+ /**
872+ * @param {...ConfigDictList } comboDimensions
873+ * @returns {ConfigDictList } N-dim Cartesian Product of combinations of the key-value-map objects from each list.
874+ */
875+ function crossCombine ( ...comboDimensions ) {
876+ function crossCombine2 ( listA , listB ) {
877+ const listC = [ ] ;
878+ for ( const a of listA ) {
879+ for ( const b of listB ) {
880+ const c = Object . assign ( { } , a , b ) ;
881+ listC . push ( c ) ;
882+ }
883+ }
884+ return listC ;
885+ }
886+
887+ let res = [ { } ] ;
888+ for ( const i in comboDimensions ) {
889+ const next = comboDimensions [ i ] || throwv ( { i, comboDimensions} ) ;
890+ res = crossCombine2 ( res , next ) ;
891+ }
892+ return res ;
893+ }
894+
895+ // -
896+
897+ /**
898+ * @typedef {number } U32 range: `[0, 0xffff_ffff]`
899+ * @typedef {number } I32 range: `[-0x8000_0000, 0x7fff_ffff]`, `0xffff_ffff|0 => -1`
900+ * @typedef {number } F32
901+ */
902+
903+ /**
904+ * "SHift Right as Uint32"
905+ * @param {U32 } val
906+ * @param {number } n
907+ * @returns {U32 }
908+ */
909+ function shr_u32 ( val , n ) {
910+ val >>= n ; // In JS this is shr_i32, with sign-extension for negative lhs.
911+
912+ if ( n > 0 ) {
913+ const result_mask = ( 1 << ( 32 - n ) ) - 1 ;
914+ val &= result_mask ;
808915 }
916+
917+ return val ;
918+ }
919+ console . assert ( ( 0xffff_ffff | 0 ) == - 1 ) ;
920+ console . assert ( 0xffff_ff00 >> 4 == ( 0xffff_fff0 | 0 ) ) ;
921+ console . assert ( shr_u32 ( 0xffff_ff00 , 4 ) != ( 0xffff_fff0 | 0 ) ) ;
922+ console . assert ( shr_u32 ( 0xffff_ff00 , 4 ) == ( 0x0fff_fff0 | 0 ) ) ;
923+ console . assert ( shr_u32 ( 0xffff_ff00 , 4 ) == 0x0fff_fff0 ) ;
924+ console . assert ( shr_u32 ( 0xffff_ff00 | 0 , 4 ) == 0x0fff_fff0 ) ;
925+
926+ /**
927+ * @type {(val: number) => U32 }
928+ */
929+ const bitcast_u32 = call ( ( ) => {
930+ const u32View = new Uint32Array ( 1 ) ;
931+ return function bitcast_u32 ( val ) {
932+ u32View [ 0 ] = val ;
933+ return u32View [ 0 ] ;
934+ } ;
935+ } ) ;
936+
937+ /**
938+ * @type {(u32: U32) => F32 }
939+ */
940+ const bitcast_f32_from_u32 = call ( ( ) => {
941+ const u32 = new Uint32Array ( 1 ) ;
942+ const f32 = new Float32Array ( u32 . buffer ) ;
943+ return function bitcast_f32_from_u32 ( v ) {
944+ u32 [ 0 ] = v ;
945+ return f32 [ 0 ] ;
946+ } ;
947+ } ) ;
948+
949+ // -
950+
951+ class PrngXorwow {
952+ /** @type {U32[] } */
953+ actual_seed ;
954+
955+ /** @type {U32[] } */
956+ state = new Uint32Array ( 6 ) ; // 5 u32 shuffler + 1 u32 counter.
957+
958+ /**
959+ * @param {U32[] | U32 | undefined } seed
960+ */
961+ constructor ( seed ) {
962+ if ( typeof ( seed ) == 'string' ) {
963+ seed = parseInt ( seed ) ;
964+ }
965+ if ( typeof ( seed ) == 'object' && seed . length !== undefined ) {
966+ // array-ish
967+ if ( ! seed . length ) {
968+ seed = new Uint32Array ( state . length ) ;
969+ crypto . getRandomValues ( seed ) ;
970+ } else {
971+ seed = new Uint32Array ( seed ) ;
972+ }
973+ } else {
974+ // number?
975+ if ( ! seed ) {
976+ seed = new Uint32Array ( 1 ) ;
977+ crypto . getRandomValues ( seed ) ;
978+ } else {
979+ seed = new Uint32Array ( [ seed ] ) ;
980+ }
981+ }
982+
983+ // Elide zeros from seed for compactness:
984+ while ( seed [ seed . length - 1 ] == 0 ) {
985+ seed = seed . slice ( 0 , seed . length - 1 ) ;
986+ }
987+ this . actual_seed = seed . slice ( ) ;
988+
989+ // Seed the state:
990+ const state = this . state ;
991+ for ( const i in state ) {
992+ state [ i ] = this . actual_seed [ i ] || 0 ;
993+ }
994+ console . assert ( state [ 0 ] || state [ 1 ] || state [ 2 ] || state [ 3 ] , "The first four words of seeded state must not all be 0:" , state )
995+
996+ }
997+
998+ /**
999+ * (n>=2)
1000+ * @returns {U32 | U32[n] }
1001+ */
1002+ seed ( ) {
1003+ const seed = this . actual_seed ;
1004+ if ( seed . length == 1 ) return seed [ 0 ] ;
1005+ return seed . slice ( ) ;
1006+ }
1007+
1008+ // -
1009+
1010+ /**
1011+ * @returns {U32[6] }
1012+ */
1013+ state ( ) {
1014+ return this . state ;
1015+ }
1016+
1017+ /**
1018+ * @returns {U32 }
1019+ */
1020+ next_u32 ( ) {
1021+ /* Algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs" */
1022+ const state = this . state ;
1023+ let t = state [ 4 ] ;
1024+
1025+ const s = state [ 0 ] ;
1026+ state [ 4 ] = state [ 3 ] ;
1027+ state [ 3 ] = state [ 2 ] ;
1028+ state [ 2 ] = state [ 1 ] ;
1029+ state [ 1 ] = s ;
1030+
1031+ t ^= shr_u32 ( t , 2 ) ;
1032+ t ^= t << 1 ;
1033+ t ^= s ^ ( s << 4 ) ;
1034+ state [ 0 ] = t ;
1035+ state [ 5 ] += 362437 ;
1036+
1037+ let ret = state [ 0 ] + state [ 5 ] ;
1038+ ret = bitcast_u32 ( ret ) ;
1039+ return ret ;
1040+ }
1041+
1042+ /**
1043+ * @returns {number } range: [0.0, 1.0)
1044+ */
1045+ next_unorm ( ) {
1046+ let ret = this . next_u32 ( ) ;
1047+ const U32_MAX = 0xffff_ffff ;
1048+ ret /= ( U32_MAX + 1 ) ;
1049+ return ret ; // [0,1)
1050+ }
1051+
1052+ /**
1053+ * A la crypto.getRandomValues()
1054+ * @param {ArrayBufferView } dest
1055+ * @returns {ArrayBufferView }
1056+ */
1057+ getRandomValues ( dest ) {
1058+ const u8s = abv_cast ( Uint8Array , dest ) ;
1059+ const len_in_u32 = Math . floor ( u8s . length / 4 ) ;
1060+ const u32s = abv_cast ( Uint32Array , u8s . subarray ( 0 , 4 * len_in_u32 ) ) ;
1061+ for ( const i in u32s ) {
1062+ u32s [ i ] = this . next_u32 ( ) ;
1063+ }
1064+ for ( const i of range ( u32s . byteLength , u8s . byteLength ) ) {
1065+ u8s [ i ] = this . next_u32 ( ) ; // Truncates u32 to u8.
1066+ }
1067+
1068+ return dest ;
1069+ }
1070+ }
1071+
1072+ // -
1073+
1074+ /**
1075+ * @template {ArrayBufferView} T
1076+ * @param {T.constructor } ctor
1077+ * @param {ArrayBufferView | ArrayBuffer } abv
1078+ * @returns {T }
1079+ */
1080+ function abv_cast ( ctor , abv ) {
1081+ if ( abv instanceof ArrayBuffer ) return new ctor ( abv ) ;
1082+ const ctor_bytes_per_element = ctor . BYTES_PER_ELEMENT || 1 ; // DataView doesn't have BYTES_PER_ELEMENT.
1083+ return new ctor ( abv . buffer , abv . byteOffset , abv . byteLength / ctor_bytes_per_element ) ;
1084+ }
1085+
1086+ // -
1087+
1088+ /**
1089+ * @returns {PrngXorwow }
1090+ */
1091+ function getDrng ( defaultSeed = 1 ) {
1092+ if ( globalThis . _DRNG ) return globalThis . _DRNG ;
1093+
1094+ const seedKeyName = `seed` ;
1095+
1096+ const url = new URL ( window . location ) ;
1097+ let requestedSeed = url . searchParams . get ( seedKeyName ) ;
1098+ if ( requestedSeed === null ) {
1099+ requestedSeed = defaultSeed ;
1100+ }
1101+
1102+ const drng = globalThis . _DRNG = new PrngXorwow ( requestedSeed ) ;
1103+ const seed = drng . seed ( ) ;
1104+
1105+ // Run it a few times to avoid seed=1 giving similar values at first.
1106+ for ( const _ of range ( 100 ) ) {
1107+ drng . next_u32 ( ) ;
1108+ }
1109+
1110+ url . searchParams . set ( seedKeyName , seed ) ;
1111+ let linkText = `Link to this run's seed: ${ url } ` ;
1112+ if ( seed != requestedSeed ) {
1113+ linkText += ' (autogenerated)' ;
1114+ }
1115+
1116+ globalThis . debug && debug ( linkText ) ;
1117+ console . log ( linkText ) ;
1118+
1119+ return drng ;
8091120}
0 commit comments