//#if 0 /** This file is for preprocessor #include into the "opfs" and "opfs-wl" impls, as well as their async-proxy part. It must be inlined in those files, as opposed to being a shared copy in the library, because (A) the async proxy does not load the library and (B) it references an object which is local to each of those files but which has a 99% identical structure for each. */ //#/if //#define vfs.metrics.enable 0 /* a refactoring crutch */ const initS11n = function(){ /** This proxy de/serializes cross-thread function arguments and output-pointer values via the state.sabIO SharedArrayBuffer, using the region defined by (state.sabS11nOffset, state.sabS11nOffset + state.sabS11nSize]. Only one dataset is recorded at a time. This is not a general-purpose format. It only supports the range of operations, and data sizes, needed by the sqlite3_vfs and sqlite3_io_methods operations. Serialized data are transient and this serialization algorithm may change at any time. The data format can be succinctly summarized as: Nt...Td...D Where: - N = number of entries (1 byte) - t = type ID of first argument (1 byte) - ...T = type IDs of the 2nd and subsequent arguments (1 byte each). - d = raw bytes of first argument (per-type size). - ...D = raw bytes of the 2nd and subsequent arguments (per-type size). All types except strings have fixed sizes. Strings are stored using their TextEncoder/TextDecoder representations. It would arguably make more sense to store them as Int16Arrays of their JS character values, but how best/fastest to get that in and out of string form is an open point. Initial experimentation with that approach did not gain us any speed. Historical note: this impl was initially about 1% this size by using using JSON.stringify/parse(), but using fit-to-purpose serialization saves considerable runtime. */ if(state.s11n) return state.s11n; const textDecoder = new TextDecoder(), textEncoder = new TextEncoder('utf-8'), viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); state.s11n = Object.create(null); /* Only arguments and return values of these types may be serialized. This covers the whole range of types needed by the sqlite3_vfs API. */ const TypeIds = Object.create(null); TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; TypeIds.string = { id: 4 }; const getTypeId = (v)=>( TypeIds[typeof v] || toss("Maintenance required: this value type cannot be serialized.",v) ); const getTypeIdById = (tid)=>{ switch(tid){ case TypeIds.number.id: return TypeIds.number; case TypeIds.bigint.id: return TypeIds.bigint; case TypeIds.boolean.id: return TypeIds.boolean; case TypeIds.string.id: return TypeIds.string; default: toss("Invalid type ID:",tid); } }; /** Returns an array of the deserialized state stored by the most recent serialize() operation (from this thread or the counterpart thread), or null if the serialization buffer is empty. If passed a truthy argument, the serialization buffer is cleared after deserialization. */ state.s11n.deserialize = function(clear=false){ //#if vfs.metrics.enable ++metrics.s11n.deserialize.count; //#/if const t = performance.now(); const argc = viewU8[0]; const rc = argc ? [] : null; if(argc){ const typeIds = []; let offset = 1, i, n, v; for(i = 0; i < argc; ++i, ++offset){ typeIds.push(getTypeIdById(viewU8[offset])); } for(i = 0; i < argc; ++i){ const t = typeIds[i]; if(t.getter){ v = viewDV[t.getter](offset, state.littleEndian); offset += t.size; }else{/*String*/ n = viewDV.getInt32(offset, state.littleEndian); offset += 4; v = textDecoder.decode(viewU8.slice(offset, offset+n)); offset += n; } rc.push(v); } } if(clear) viewU8[0] = 0; //log("deserialize:",argc, rc); //#if vfs.metrics.enable metrics.s11n.deserialize.time += performance.now() - t; //#/if return rc; }; /** Serializes all arguments to the shared buffer for consumption by the counterpart thread. This routine is only intended for serializing OPFS VFS arguments and (in at least one special case) result values, and the buffer is sized to be able to comfortably handle those. If passed no arguments then it zeroes out the serialization state. */ state.s11n.serialize = function(...args){ const t = performance.now(); //#if vfs.metrics.enable ++metrics.s11n.serialize.count; //#/if if(args.length){ //log("serialize():",args); const typeIds = []; let i = 0, offset = 1; viewU8[0] = args.length & 0xff /* header = # of args */; for(; i < args.length; ++i, ++offset){ /* Write the TypeIds.id value into the next args.length bytes. */ typeIds.push(getTypeId(args[i])); viewU8[offset] = typeIds[i].id; } for(i = 0; i < args.length; ++i) { /* Deserialize the following bytes based on their corresponding TypeIds.id from the header. */ const t = typeIds[i]; if(t.setter){ viewDV[t.setter](offset, args[i], state.littleEndian); offset += t.size; }else{/*String*/ const s = textEncoder.encode(args[i]); viewDV.setInt32(offset, s.byteLength, state.littleEndian); offset += 4; viewU8.set(s, offset); offset += s.byteLength; } } //log("serialize() result:",viewU8.slice(0,offset)); }else{ viewU8[0] = 0; } //#if vfs.metrics.enable metrics.s11n.serialize.time += performance.now() - t; //#/if }; //#if defined opfs-async-proxy state.s11n.storeException = state.asyncS11nExceptions ? ((priority,e)=>{ if(priority<=state.asyncS11nExceptions){ state.s11n.serialize([e.name,': ',e.message].join("")); } }) : ()=>{}; //#/if return state.s11n; //#undef vfs.metrics.enable }/*initS11n()*/;