Move the JS pieces of kvvfs into their own file to facilitate pending feature experimentation.
FossilOrigin-Name: 3c40614285449df259a3444e36f888cfb5e782ea58287914f97f496ea61e9e9f
This commit is contained in:
@@ -910,6 +910,7 @@ sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js
|
||||
ifeq (0,$(wasm-bare-bones))
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js
|
||||
endif
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-kvvfs.c-pp.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
|
||||
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js
|
||||
|
||||
|
||||
@@ -988,8 +988,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
const notThese = Object.assign(Object.create(null),{
|
||||
// For each struct to NOT register, map its name to true:
|
||||
WasmTestStruct: true,
|
||||
/* We unregister the kvvfs VFS from Worker threads below. */
|
||||
sqlite3_kvvfs_methods: !util.isUIThread(),
|
||||
/* sqlite3_index_info and friends require int64: */
|
||||
sqlite3_index_info: !wasm.bigIntEnabled,
|
||||
sqlite3_index_constraint: !wasm.bigIntEnabled,
|
||||
@@ -1783,106 +1781,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
};
|
||||
}/* auto-extension */
|
||||
|
||||
const pKvvfs = capi.sqlite3_vfs_find("kvvfs");
|
||||
if( pKvvfs ){/* kvvfs-specific glue */
|
||||
if(util.isUIThread()){
|
||||
const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
|
||||
wasm.exports.sqlite3__wasm_kvvfs_methods()
|
||||
);
|
||||
delete capi.sqlite3_kvvfs_methods;
|
||||
|
||||
const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack,
|
||||
pstack = wasm.pstack;
|
||||
|
||||
const kvvfsStorage = (zClass)=>
|
||||
((115/*=='s'*/===wasm.peek(zClass))
|
||||
? sessionStorage : localStorage);
|
||||
|
||||
/**
|
||||
Implementations for members of the object referred to by
|
||||
sqlite3__wasm_kvvfs_methods(). We swap out the native
|
||||
implementations with these, which use localStorage or
|
||||
sessionStorage for their backing store.
|
||||
*/
|
||||
const kvvfsImpls = {
|
||||
xRead: (zClass, zKey, zBuf, nBuf)=>{
|
||||
const stack = pstack.pointer,
|
||||
astack = wasm.scopedAllocPush();
|
||||
try {
|
||||
const zXKey = kvvfsMakeKey(zClass,zKey);
|
||||
if(!zXKey) return -3/*OOM*/;
|
||||
const jKey = wasm.cstrToJs(zXKey);
|
||||
const jV = kvvfsStorage(zClass).getItem(jKey);
|
||||
if(!jV) return -1;
|
||||
const nV = jV.length /* We are relying 100% on v being
|
||||
ASCII so that jV.length is equal
|
||||
to the C-string's byte length. */;
|
||||
if(nBuf<=0) return nV;
|
||||
else if(1===nBuf){
|
||||
wasm.poke(zBuf, 0);
|
||||
return nV;
|
||||
}
|
||||
const zV = wasm.scopedAllocCString(jV);
|
||||
if(nBuf > nV + 1) nBuf = nV + 1;
|
||||
wasm.heap8u().copyWithin(
|
||||
Number(zBuf), Number(zV), wasm.ptr.addn(zV, nBuf,- 1)
|
||||
);
|
||||
wasm.poke(wasm.ptr.add(zBuf, nBuf, -1), 0);
|
||||
return nBuf - 1;
|
||||
}catch(e){
|
||||
sqlite3.config.error("kvstorageRead()",e);
|
||||
return -2;
|
||||
}finally{
|
||||
pstack.restore(stack);
|
||||
wasm.scopedAllocPop(astack);
|
||||
}
|
||||
},
|
||||
xWrite: (zClass, zKey, zData)=>{
|
||||
const stack = pstack.pointer;
|
||||
try {
|
||||
const zXKey = kvvfsMakeKey(zClass,zKey);
|
||||
if(!zXKey) return 1/*OOM*/;
|
||||
const jKey = wasm.cstrToJs(zXKey);
|
||||
kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
|
||||
return 0;
|
||||
}catch(e){
|
||||
sqlite3.config.error("kvstorageWrite()",e);
|
||||
return capi.SQLITE_IOERR;
|
||||
}finally{
|
||||
pstack.restore(stack);
|
||||
}
|
||||
},
|
||||
xDelete: (zClass, zKey)=>{
|
||||
const stack = pstack.pointer;
|
||||
try {
|
||||
const zXKey = kvvfsMakeKey(zClass,zKey);
|
||||
if(!zXKey) return 1/*OOM*/;
|
||||
kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
|
||||
return 0;
|
||||
}catch(e){
|
||||
sqlite3.config.error("kvstorageDelete()",e);
|
||||
return capi.SQLITE_IOERR;
|
||||
}finally{
|
||||
pstack.restore(stack);
|
||||
}
|
||||
}
|
||||
}/*kvvfsImpls*/;
|
||||
for(const k of Object.keys(kvvfsImpls)){
|
||||
kvvfsMethods[kvvfsMethods.memberKey(k)] =
|
||||
wasm.installFunction(
|
||||
kvvfsMethods.memberSignature(k),
|
||||
kvvfsImpls[k]
|
||||
);
|
||||
}
|
||||
}else{
|
||||
/* Worker thread: unregister kvvfs to avoid it being used
|
||||
for anything other than local/sessionStorage. It "can"
|
||||
be used that way but it's not really intended to be. */
|
||||
capi.sqlite3_vfs_unregister(pKvvfs);
|
||||
delete capi.sqlite3_kvvfs_methods;
|
||||
}
|
||||
}/*pKvvfs*/
|
||||
|
||||
/* Warn if client-level code makes use of FuncPtrAdapter. */
|
||||
wasm.xWrap.FuncPtrAdapter.warnOnUse = true;
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
*/
|
||||
ctor._name2vfs = Object.create(null);
|
||||
const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/)
|
||||
? (n)=>toss3("The VFS for",n,"is only available in the main window thread.")
|
||||
? (n)=>toss3("VFS",n,"is only available in the main window thread.")
|
||||
: false;
|
||||
ctor._name2vfs[':localStorage:'] = {
|
||||
vfs: 'kvvfs', filename: isWorkerThread || (()=>'local')
|
||||
@@ -2295,55 +2295,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
Stmt
|
||||
}/*oo1 object*/;
|
||||
|
||||
if(util.isUIThread()){
|
||||
/**
|
||||
Functionally equivalent to DB(storageName,'c','kvvfs') except
|
||||
that it throws if the given storage name is not one of 'local'
|
||||
or 'session'.
|
||||
|
||||
As of version 3.46, the argument may optionally be an options
|
||||
object in the form:
|
||||
|
||||
{
|
||||
filename: 'session'|'local',
|
||||
... etc. (all options supported by the DB ctor)
|
||||
}
|
||||
|
||||
noting that the 'vfs' option supported by main DB
|
||||
constructor is ignored here: the vfs is always 'kvvfs'.
|
||||
*/
|
||||
sqlite3.oo1.JsStorageDb = function(storageName='session'){
|
||||
const opt = dbCtorHelper.normalizeArgs(...arguments);
|
||||
storageName = opt.filename;
|
||||
if('session'!==storageName && 'local'!==storageName){
|
||||
toss3("JsStorageDb db name must be one of 'session' or 'local'.");
|
||||
}
|
||||
opt.vfs = 'kvvfs';
|
||||
dbCtorHelper.call(this, opt);
|
||||
};
|
||||
const jdb = sqlite3.oo1.JsStorageDb;
|
||||
jdb.prototype = Object.create(DB.prototype);
|
||||
/** Equivalent to sqlite3_js_kvvfs_clear(). */
|
||||
jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
|
||||
/**
|
||||
Clears this database instance's storage or throws if this
|
||||
instance has been closed. Returns the number of
|
||||
database blocks which were cleaned up.
|
||||
*/
|
||||
jdb.prototype.clearStorage = function(){
|
||||
return jdb.clearStorage(affirmDbOpen(this).filename);
|
||||
};
|
||||
/** Equivalent to sqlite3_js_kvvfs_size(). */
|
||||
jdb.storageSize = capi.sqlite3_js_kvvfs_size;
|
||||
/**
|
||||
Returns the _approximate_ number of bytes this database takes
|
||||
up in its storage or throws if this instance has been closed.
|
||||
*/
|
||||
jdb.prototype.storageSize = function(){
|
||||
return jdb.storageSize(affirmDbOpen(this).filename);
|
||||
};
|
||||
}/*main-window-only bits*/
|
||||
|
||||
});
|
||||
//#else
|
||||
/* Built with the omit-oo1 flag. */
|
||||
|
||||
@@ -1604,86 +1604,6 @@ globalThis.sqlite3ApiBootstrap = async function sqlite3ApiBootstrap(
|
||||
return x===v ? undefined : x;
|
||||
}
|
||||
|
||||
if( util.isUIThread() ){
|
||||
/* Features specific to the main window thread... */
|
||||
|
||||
/**
|
||||
Internal helper for sqlite3_js_kvvfs_clear() and friends.
|
||||
Its argument should be one of ('local','session',"").
|
||||
*/
|
||||
const __kvvfsInfo = function(which){
|
||||
const rc = Object.create(null);
|
||||
rc.prefix = 'kvvfs-'+which;
|
||||
rc.stores = [];
|
||||
if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage);
|
||||
if('local'===which || ""===which) rc.stores.push(globalThis.localStorage);
|
||||
return rc;
|
||||
};
|
||||
|
||||
/**
|
||||
Clears all storage used by the kvvfs DB backend, deleting any
|
||||
DB(s) stored there. Its argument must be either 'session',
|
||||
'local', or "". In the first two cases, only sessionStorage
|
||||
resp. localStorage is cleared. If it's an empty string (the
|
||||
default) then both are cleared. Only storage keys which match
|
||||
the pattern used by kvvfs are cleared: any other client-side
|
||||
data are retained.
|
||||
|
||||
This function is only available in the main window thread.
|
||||
|
||||
Returns the number of entries cleared.
|
||||
*/
|
||||
capi.sqlite3_js_kvvfs_clear = function(which=""){
|
||||
let rc = 0;
|
||||
const kvinfo = __kvvfsInfo(which);
|
||||
kvinfo.stores.forEach((s)=>{
|
||||
const toRm = [] /* keys to remove */;
|
||||
let i;
|
||||
for( i = 0; i < s.length; ++i ){
|
||||
const k = s.key(i);
|
||||
if(k.startsWith(kvinfo.prefix)) toRm.push(k);
|
||||
}
|
||||
toRm.forEach((kk)=>s.removeItem(kk));
|
||||
rc += toRm.length;
|
||||
});
|
||||
return rc;
|
||||
};
|
||||
|
||||
/**
|
||||
This routine guesses the approximate amount of
|
||||
window.localStorage and/or window.sessionStorage in use by the
|
||||
kvvfs database backend. Its argument must be one of
|
||||
('session', 'local', ""). In the first two cases, only
|
||||
sessionStorage resp. localStorage is counted. If it's an empty
|
||||
string (the default) then both are counted. Only storage keys
|
||||
which match the pattern used by kvvfs are counted. The returned
|
||||
value is the "length" value of every matching key and value,
|
||||
noting that JavaScript stores each character in 2 bytes.
|
||||
|
||||
Note that the returned size is not authoritative from the
|
||||
perspective of how much data can fit into localStorage and
|
||||
sessionStorage, as the precise algorithms for determining
|
||||
those limits are unspecified and may include per-entry
|
||||
overhead invisible to clients.
|
||||
*/
|
||||
capi.sqlite3_js_kvvfs_size = function(which=""){
|
||||
let sz = 0;
|
||||
const kvinfo = __kvvfsInfo(which);
|
||||
kvinfo.stores.forEach((s)=>{
|
||||
let i;
|
||||
for(i = 0; i < s.length; ++i){
|
||||
const k = s.key(i);
|
||||
if(k.startsWith(kvinfo.prefix)){
|
||||
sz += k.length;
|
||||
sz += s.getItem(k).length;
|
||||
}
|
||||
}
|
||||
});
|
||||
return sz * 2 /* because JS uses 2-byte char encoding */;
|
||||
};
|
||||
|
||||
}/* main-window-only bits */
|
||||
|
||||
/**
|
||||
Wraps all known variants of the C-side variadic
|
||||
sqlite3_db_config().
|
||||
|
||||
+326
-146
@@ -30,19 +30,32 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
all args with a space between each. */
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
|
||||
if(!(config.heap instanceof WebAssembly.Memory)
|
||||
&& !(config.heap instanceof Function)){
|
||||
toss("config.heap must be WebAssembly.Memory instance or a function which returns one.");
|
||||
{
|
||||
let h = config.heap;
|
||||
if( h instanceof WebAssembly.Memory ){
|
||||
h = function(){return new Uint8Array(this.buffer)}.bind(h);
|
||||
}else if( !(h instanceof Function) ){
|
||||
//console.warn("The bothersome StructBinderFactory config:",config);
|
||||
toss("config.heap must be WebAssembly.Memory instance or",
|
||||
"a function which returns one.");
|
||||
}
|
||||
config.heap = h;
|
||||
}
|
||||
['alloc','dealloc'].forEach(function(k){
|
||||
(config[k] instanceof Function) ||
|
||||
toss("Config option '"+k+"' must be a function.");
|
||||
});
|
||||
const __heap = config.heap;
|
||||
const SBF = StructBinderFactory;
|
||||
const heap = __heap ? __heap : ()=>new Uint8Array(__heap.buffer),
|
||||
const heap = config.heap,
|
||||
alloc = config.alloc,
|
||||
dealloc = config.dealloc,
|
||||
realloc = (config.realloc || function(){
|
||||
toss("This StructBinderFactory was configured without realloc()");
|
||||
/* We can't know the original memory's size from here unless
|
||||
we internally proxy alloc()/dealloc() to track all
|
||||
pointers (not going to happen), so we can't fall back to
|
||||
doing alloc()/copy/dealloc(). */
|
||||
}),
|
||||
log = config.log || console.debug.bind(console),
|
||||
memberPrefix = (config.memberPrefix || ""),
|
||||
memberSuffix = (config.memberSuffix || ""),
|
||||
@@ -51,10 +64,10 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
bigIntEnabled = config.bigIntEnabled ?? !!BigInt64Array;
|
||||
|
||||
//console.warn("config",config);
|
||||
let ptr = alloc(1);
|
||||
let ptr;
|
||||
const ptrIR = config.pointerIR
|
||||
|| config.ptrIR/*deprecated*/
|
||||
|| ('bigint'===typeof ptr ? 'i64' : 'i32');
|
||||
|| config.ptrIR/*deprecated*/
|
||||
|| ('bigint'===typeof (ptr = alloc(1)) ? 'i64' : 'i32');
|
||||
/* Undocumented (on purpose) config options: */
|
||||
const ptrSize = config.ptrSize/*deprecated*/
|
||||
|| ('i32'===ptrIR ? 4 : 8);
|
||||
@@ -91,6 +104,10 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
return rc;
|
||||
};
|
||||
|
||||
const __ptrAddSelf = function(...args){
|
||||
return __ptrAdd(this.pointer,...args);
|
||||
};
|
||||
|
||||
if(!SBF.debugFlags){
|
||||
SBF.__makeDebugFlags = function(deriveFrom=null){
|
||||
/* This is disgustingly overengineered. :/ */
|
||||
@@ -120,12 +137,12 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
SBF.debugFlags = SBF.__makeDebugFlags();
|
||||
}/*static init*/
|
||||
|
||||
const isLittleEndian = (function() {
|
||||
const isLittleEndian = true || (function() {
|
||||
const buffer = new ArrayBuffer(2);
|
||||
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
|
||||
// Int16Array uses the platform's endianness.
|
||||
return new Int16Array(buffer)[0] === 256;
|
||||
})();
|
||||
})() /* WASM is, by definition, Little Endian */;
|
||||
|
||||
/**
|
||||
Some terms used in the internal docs:
|
||||
@@ -146,8 +163,9 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/;
|
||||
/** Returns p if SIG s is a function SIG, else returns s[0]. */
|
||||
const sigLetter = (s)=>s ? (isFuncSig(s) ? 'p' : s[0]) : undefined;
|
||||
/** Returns the WASM IR form of the Emscripten-conventional letter
|
||||
at SIG s[0]. Throws for an unknown SIG. */
|
||||
|
||||
/** Returns the WASM IR form of the letter at SIG s[0]. Throws for
|
||||
an unknown SIG. */
|
||||
const sigIR = function(s){
|
||||
switch(sigLetter(s)){
|
||||
case 'c': case 'C': return 'i8';
|
||||
@@ -159,8 +177,9 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
}
|
||||
toss("Unhandled signature IR:",s);
|
||||
};
|
||||
/** Returns the WASM sizeof of the Emscripten-conventional letter
|
||||
at SIG s[0]. Throws for an unknown SIG. */
|
||||
|
||||
/** Returns the WASM sizeof of the letter at SIG s[0]. Throws for an
|
||||
unknown SIG. */
|
||||
const sigSize = function(s){
|
||||
switch(sigLetter(s)){
|
||||
case 'c': case 'C': return 1;
|
||||
@@ -175,6 +194,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
|
||||
const affirmBigIntArray = BigInt64Array
|
||||
? ()=>true : ()=>toss('BigInt64Array is not available.');
|
||||
|
||||
/** Returns the name of a DataView getter method corresponding
|
||||
to the given SIG. */
|
||||
const sigDVGetter = function(s){
|
||||
@@ -195,6 +215,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
}
|
||||
toss("Unhandled DataView getter for signature:",s);
|
||||
};
|
||||
|
||||
/** Returns the name of a DataView setter method corresponding
|
||||
to the given SIG. */
|
||||
const sigDVSetter = function(s){
|
||||
@@ -246,26 +267,44 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
/**
|
||||
In order to completely hide StructBinder-bound struct pointers
|
||||
from JS code, we store them in a scope-local WeakMap which maps
|
||||
the struct-bound objects to their WASM pointers. The pointers are
|
||||
accessible via boundObject.pointer, which is gated behind a
|
||||
property interceptor, but are not exposed anywhere else in the
|
||||
object.
|
||||
the struct-bound objects to an object with their metadata:
|
||||
|
||||
This approach means we cannot proxy arrays, or any type which
|
||||
might be realloced, as that pointer could change out from under
|
||||
us. That's not an issue for nested structs, but it might be for a
|
||||
struct they're embedded in. In the case of nested structs we
|
||||
"could" record their top-most parent object and their offset into
|
||||
that object, instead of storing the pointer itself. We could that
|
||||
by allowing a function instead of a pointer in this map, that
|
||||
function returning the (lazily-calculated) address. Hmm.
|
||||
{
|
||||
.p = the native pointer,
|
||||
.o = self (for an eventual reverse-mapping),
|
||||
.xb = extra bytes allocated for p,
|
||||
.zod = zeroOnDispose,
|
||||
.ownsPointer = true if this object owns p
|
||||
}
|
||||
|
||||
The .p data are accessible via obj.pointer, which is gated behind
|
||||
a property interceptor, but are not exposed anywhere else in the
|
||||
public API.
|
||||
*/
|
||||
const __instancePointerMap = new WeakMap();
|
||||
const getInstanceHandle = function f(obj, create=true){
|
||||
let ii = f.map.get(obj);
|
||||
if( !ii && create ){
|
||||
f.map.set(obj, (ii=f.create(obj)));
|
||||
}
|
||||
return ii;
|
||||
};
|
||||
getInstanceHandle.map = new WeakMap;
|
||||
getInstanceHandle.create = (forObj)=>{
|
||||
return Object.assign(Object.create(null),{
|
||||
o: forObj,
|
||||
p: undefined/*native ptr*/,
|
||||
ownsPointer: false,
|
||||
zod: false/*zeroOnDispose*/,
|
||||
xb: 0/*extraBytes*/
|
||||
});
|
||||
};
|
||||
|
||||
const getInstancePtr = (obj)=>__instancePointerMap.get(obj);
|
||||
|
||||
/** Property name for the pointer-is-external marker. */
|
||||
const xPtrPropName = Symbol('(pointer-is-external)');
|
||||
/**
|
||||
Remove the getInstanceHandle() mapping for obj.
|
||||
*/
|
||||
const rmInstanceHandle = (obj)=>getInstanceHandle.map.delete(obj)
|
||||
/* If/when we have a reverse map of ptr-to-objects, we need to
|
||||
clean that here. */;
|
||||
|
||||
const __isPtr32 = (ptr)=>('number'===typeof ptr && (ptr===(ptr|0)) && ptr>=0);
|
||||
const __isPtr64 = (ptr)=>(
|
||||
@@ -278,85 +317,127 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
*/
|
||||
const __isPtr = (4===ptrSize) ? __isPtr32 : __isPtr64;
|
||||
|
||||
/** Frees the obj.pointer memory and clears the pointer
|
||||
property. */
|
||||
const __isNonNullPtr = (v)=>__isPtr(v) && (v>0);
|
||||
|
||||
/** Frees the obj.pointer memory (a.k.a. m), handles obj.ondispose,
|
||||
and unmaps obj from its native resources. */
|
||||
const __freeStruct = function(ctor, obj, m){
|
||||
if(!m) m = getInstancePtr(obj);
|
||||
if(m) {
|
||||
__instancePointerMap.delete(obj);
|
||||
if(Array.isArray(obj.ondispose)){
|
||||
let x;
|
||||
while((x = obj.ondispose.shift())){
|
||||
try{
|
||||
if(x instanceof Function) x.call(obj);
|
||||
else if(x instanceof StructType) x.dispose();
|
||||
else if(__isPtr(x)) dealloc(x);
|
||||
// else ignore. Strings are permitted to annotate entries
|
||||
// to assist in debugging.
|
||||
}catch(e){
|
||||
console.warn("ondispose() for",ctor.structName,'@',
|
||||
m,'threw. NOT propagating it.',e);
|
||||
}
|
||||
}
|
||||
}else if(obj.ondispose instanceof Function){
|
||||
try{obj.ondispose()}
|
||||
catch(e){
|
||||
/*do not rethrow: destructors must not throw*/
|
||||
const ii = getInstanceHandle(obj, false);
|
||||
if( !ii ) return;
|
||||
rmInstanceHandle(obj);
|
||||
if( !m && !(m = ii.p) ){
|
||||
console.warn("Cannot(?) happen: __freeStruct() found no instanceInfo");
|
||||
return;
|
||||
}
|
||||
if(Array.isArray(obj.ondispose)){
|
||||
let x;
|
||||
while((x = obj.ondispose.pop())){
|
||||
try{
|
||||
if(x instanceof Function) x.call(obj);
|
||||
else if(x instanceof StructType) x.dispose();
|
||||
else if(__isPtr(x)) dealloc(x);
|
||||
// else ignore. Strings are permitted to annotate entries
|
||||
// to assist in debugging.
|
||||
}catch(e){
|
||||
console.warn("ondispose() for",ctor.structName,'@',
|
||||
m,'threw. NOT propagating it.',e);
|
||||
}
|
||||
}
|
||||
delete obj.ondispose;
|
||||
if(ctor.debugFlags.__flags.dealloc){
|
||||
log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""),
|
||||
ctor.structName,"instance:",
|
||||
ctor.structInfo.sizeof,"bytes @"+m);
|
||||
}
|
||||
if(!obj[xPtrPropName]){
|
||||
if( ctor.structInfo.zeroOnFree ){
|
||||
heap().fill(0, Number(m), Number(m)+ctor.structInfo.sizeof);
|
||||
}
|
||||
dealloc(m);
|
||||
}else if(obj.ondispose instanceof Function){
|
||||
try{obj.ondispose()}
|
||||
catch(e){
|
||||
/*do not rethrow: destructors must not throw*/
|
||||
console.warn("ondispose() for",ctor.structName,'@',
|
||||
m,'threw. NOT propagating it.',e);
|
||||
}
|
||||
}
|
||||
delete obj.ondispose;
|
||||
if(ctor.debugFlags.__flags.dealloc){
|
||||
log("debug.dealloc:",(ii.ownsPointer?"":"EXTERNAL"),
|
||||
ctor.structName,"instance:",
|
||||
ctor.structInfo.sizeof,"bytes @"+m);
|
||||
}
|
||||
if(ii.ownsPointer){
|
||||
if( ii.zod || ctor.structInfo.zeroOnDispose ){
|
||||
heap().fill(0, Number(m),
|
||||
Number(m) + ctor.structInfo.sizeof + ii.xb);
|
||||
}
|
||||
dealloc(m);
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns a skeleton for a read-only, non-iterable property
|
||||
* descriptor. */
|
||||
const rop0 = ()=>{return {configurable: false, writable: false,
|
||||
iterable: false}};
|
||||
|
||||
/** Returns a skeleton for a read-only property accessor wrapping
|
||||
value v. */
|
||||
const rop = (v)=>{return {configurable: false, writable: false,
|
||||
iterable: false, value: v}};
|
||||
const rop = (v)=>{return {...rop0(), value: v}};
|
||||
|
||||
/** Allocates obj's memory buffer based on the size defined in
|
||||
ctor.structInfo.sizeof. */
|
||||
const __allocStruct = function(ctor, obj, m){
|
||||
let fill = !m;
|
||||
if(m) Object.defineProperty(obj, xPtrPropName, rop(m));
|
||||
else{
|
||||
m = alloc(ctor.structInfo.sizeof);
|
||||
if(!m) toss("Allocation of",ctor.structName,"structure failed.");
|
||||
const __allocStruct = function f(ctor, obj, xm){
|
||||
let opt;
|
||||
const checkPtr = (ptr)=>{
|
||||
__isNonNullPtr(ptr) ||
|
||||
toss("Invalid pointer value",arguments[0],"for",ctor.structName,"constructor.");
|
||||
};
|
||||
if( arguments.length>=3 ){
|
||||
if( xm && ('object'===typeof xm) ){
|
||||
opt = xm;
|
||||
xm = opt?.wrap;
|
||||
}else{
|
||||
checkPtr(xm);
|
||||
opt = {wrap: xm};
|
||||
}
|
||||
}else{
|
||||
opt = {}
|
||||
}
|
||||
|
||||
const fill = !xm /* true if we need to zero the memory */;
|
||||
let nAlloc = 0;
|
||||
let ownsPointer = false;
|
||||
if(xm){
|
||||
/* Externally-allocated memory. */
|
||||
checkPtr(xm);
|
||||
ownsPointer = !!opt?.takeOwnership;
|
||||
}else{
|
||||
const nX = opt?.extraBytes ?? 0;
|
||||
if( nX<0 || (nX!==(nX|0)) ){
|
||||
toss("Invalid extraBytes value:",opt?.extraBytes);
|
||||
}
|
||||
nAlloc = ctor.structInfo.sizeof + nX;
|
||||
xm = alloc(nAlloc)
|
||||
|| toss("Allocation of",ctor.structName,"structure failed.");
|
||||
ownsPointer = true;
|
||||
}
|
||||
try {
|
||||
if(ctor.debugFlags.__flags.alloc){
|
||||
if( opt?.debugFlags ){
|
||||
/* specifically undocumented */
|
||||
obj.debugFlags(opt.debugFlags);
|
||||
}
|
||||
if(ctor./*prototype.???*/debugFlags.__flags.alloc){
|
||||
log("debug.alloc:",(fill?"":"EXTERNAL"),
|
||||
ctor.structName,"instance:",
|
||||
ctor.structInfo.sizeof,"bytes @"+m);
|
||||
ctor.structInfo.sizeof,"bytes @"+xm);
|
||||
}
|
||||
if(fill){
|
||||
heap().fill(0, Number(m), Number(m) + ctor.structInfo.sizeof);
|
||||
heap().fill(0, Number(xm), Number(xm) + nAlloc);
|
||||
}
|
||||
const ii = getInstanceHandle(obj);
|
||||
ii.p = xm;
|
||||
ii.ownsPointer = ownsPointer;
|
||||
ii.xb = nAlloc ? (nAlloc-ctor.structInfo.sizeof) : 0;
|
||||
ii.zod = !!opt?.zeroOnDispose;
|
||||
if( opt?.ondispose && opt.ondispose!==xm ){
|
||||
obj.addOnDispose( opt.ondispose );
|
||||
}
|
||||
__instancePointerMap.set(obj, m);
|
||||
}catch(e){
|
||||
__freeStruct(ctor, obj, m);
|
||||
__freeStruct(ctor, obj, xm);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
/** Gets installed as the memoryDump() method of all structs. */
|
||||
const __memoryDump = function(){
|
||||
const p = this.pointer;
|
||||
return p
|
||||
? new Uint8Array(heap().slice(Number(p), Number(p) + this.structInfo.sizeof))
|
||||
: null;
|
||||
};
|
||||
|
||||
/** True if sig looks like an emscripten/jaccwabyt
|
||||
type signature, else false. */
|
||||
@@ -390,25 +471,33 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
|
||||
// StructBinder::adaptGet()
|
||||
const __adaptGet = function(key, ...args){
|
||||
/*if( looksLikeASig(key) ){
|
||||
toss("Getter adaptor's name (",key,") collides with a data type signature.");
|
||||
}*/
|
||||
return __adaptor(this, 0, key, ...args);
|
||||
};
|
||||
|
||||
const __affirmNotASig = function(ctx,key){
|
||||
looksLikeASig(key) &&
|
||||
toss(ctx,"(",key,") collides with a data type signature.");
|
||||
};
|
||||
|
||||
// StructBinder::adaptSet()
|
||||
const __adaptSet = function(key, ...args){
|
||||
if( looksLikeASig(key) ){
|
||||
toss("Setter adaptor's name (",key,") collides with a data type signature.");
|
||||
}
|
||||
__affirmNotASig('Setter adaptor',key);
|
||||
return __adaptor(this, 1, key, ...args);
|
||||
};
|
||||
|
||||
// StructBinder::adaptStruct()
|
||||
const __adaptStruct = function(key, ...args){
|
||||
__affirmNotASig('Struct adaptor',key);
|
||||
return __adaptor(this, 2, key, ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
An internal counterpart of __adaptStruct(). If key is-a string,
|
||||
uses __adaptor(who) to fetch the struct-adaptor entry for key,
|
||||
else key is assumed to be a struct description object. If it
|
||||
resolves to an object, that's returned, else an exception is
|
||||
thrown.
|
||||
*/
|
||||
const __adaptStruct2 = function(who,key){
|
||||
const si = ('string'===typeof key)
|
||||
? __adaptor(who, 2, key) : key;
|
||||
@@ -455,15 +544,6 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
return emscriptenFormat ? f._(m.signature) : m.signature;
|
||||
};
|
||||
|
||||
const __ptrPropDescriptor = {
|
||||
configurable: false, enumerable: false,
|
||||
get: function(){return getInstancePtr(this)},
|
||||
set: ()=>toss("Cannot assign the 'pointer' property of a struct.")
|
||||
// Reminder: leaving `set` undefined makes assignments
|
||||
// to the property _silently_ do nothing. Current unit tests
|
||||
// rely on it throwing, though.
|
||||
};
|
||||
|
||||
/** Impl of X.memberKeys() for StructType and struct ctors. */
|
||||
const __structMemberKeys = rop(function(){
|
||||
const a = [];
|
||||
@@ -597,13 +677,13 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
Prototype for all StructFactory instances (the constructors
|
||||
returned from StructBinder).
|
||||
*/
|
||||
const StructType = function ctor(structName, structInfo){
|
||||
if(arguments[2]!==rop){
|
||||
const StructType = function StructType(structName, structInfo){
|
||||
if(arguments[2]!==rop/*internal sentinel value*/){
|
||||
toss("Do not call the StructType constructor",
|
||||
"from client-level code.");
|
||||
}
|
||||
Object.defineProperties(this,{
|
||||
//isA: rop((v)=>v instanceof ctor),
|
||||
//isA: rop((v)=>v instanceof StructType),
|
||||
structName: rop(structName),
|
||||
structInfo: rop(structInfo)
|
||||
});
|
||||
@@ -629,8 +709,31 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
memberSignature: rop(function(memberName, emscriptenFormat=false){
|
||||
return __memberSignature(this, memberName, emscriptenFormat);
|
||||
}),
|
||||
memoryDump: rop(__memoryDump),
|
||||
pointer: __ptrPropDescriptor,
|
||||
memoryDump: rop(function(){
|
||||
const p = this.pointer;
|
||||
return p
|
||||
? new Uint8Array(heap().slice(Number(p), Number(p) + this.structInfo.sizeof))
|
||||
: null;
|
||||
}),
|
||||
extraBytes: {
|
||||
configurable: false, enumerable: false,
|
||||
get: function(){return getInstanceHandle(this, false)?.xb ?? 0;}
|
||||
},
|
||||
zeroOnDispose: {
|
||||
configurable: false, enumerable: false,
|
||||
get: function(){
|
||||
return getInstanceHandle(this, false)?.zod
|
||||
?? !!this.structInfo.zeroOnDispose;
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
configurable: false, enumerable: false,
|
||||
get: function(){return getInstanceHandle(this, false)?.p},
|
||||
set: ()=>toss("Cannot assign the 'pointer' property of a struct.")
|
||||
// Reminder: leaving `set` undefined makes assignments
|
||||
// to the property _silently_ do nothing. Current unit tests
|
||||
// rely on it throwing, though.
|
||||
},
|
||||
setMemberCString: rop(function(memberName, str){
|
||||
return __setMemberCString(this, memberName, str);
|
||||
})
|
||||
@@ -649,8 +752,12 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
Object.defineProperties(StructType, {
|
||||
allocCString: rop(__allocCString),
|
||||
isA: rop((v)=>v instanceof StructType),
|
||||
hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]),
|
||||
hasExternalPointer: rop((v)=>{
|
||||
const ii = getInstanceHandle(v, false);
|
||||
return !!(ii?.p && !ii?.ownsPointer);
|
||||
}),
|
||||
memberKey: __memberKeyProp
|
||||
//ptrAdd = rop(__ptrAdd) no b/c one might think that it adds based on this.pointer.
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -693,7 +800,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
do any permanent harm, as the wrapper object will be recreated
|
||||
when accessing f.bar, pointing to the same memory in f.
|
||||
|
||||
The si.zeroOnFree flag has no effect on embedded structs because
|
||||
The si.zeroOnDispose flag has no effect on embedded structs because
|
||||
they wrap "external" memory, so do not own it, and are thus never
|
||||
freed, as such.
|
||||
*/
|
||||
@@ -725,7 +832,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
enumerable: false,
|
||||
set: __propThrowOnSet(ctor/*not si.constructor*/.structName, key),
|
||||
get: function(){
|
||||
const dbg = si.constructor.prototype.debugFlags.__flags;
|
||||
const dbg = this.debugFlags.__flags;
|
||||
const p = this.pointer;
|
||||
const k = p+'.'+key;
|
||||
let s = __innerStructs.get(k);
|
||||
@@ -758,16 +865,16 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
return makeMemberStructWrapper.call(this, ctor, name, si);
|
||||
}
|
||||
|
||||
if(!f._){
|
||||
if(!f.cache){
|
||||
/* Cache all available getters/setters/set-wrappers for
|
||||
direct reuse in each accessor function. */
|
||||
f._ = {getters: {}, setters: {}, sw:{}};
|
||||
f.cache = {getters: {}, setters: {}, sw:{}};
|
||||
const a = ['i','c','C','p','P','s','f','d','v()'];
|
||||
if(bigIntEnabled) a.push('j');
|
||||
a.forEach(function(v){
|
||||
f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */;
|
||||
f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */;
|
||||
f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values
|
||||
f.cache.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */;
|
||||
f.cache.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */;
|
||||
f.cache.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values
|
||||
for conversion */;
|
||||
});
|
||||
f.sigCheck = function(obj, name, key,sig){
|
||||
@@ -784,8 +891,8 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
si.key = key;
|
||||
si.name = name;
|
||||
const sigGlyph = sigLetter(si.signature);
|
||||
const xPropName = sPropName(ctor.prototype.structName,key);
|
||||
const dbg = ctor.prototype.debugFlags.__flags;
|
||||
const xPropName = sPropName(ctor.structName,key);
|
||||
const dbg = ctor.debugFlags.__flags;
|
||||
/*
|
||||
TODO?: set prototype of si to an object which can set/fetch
|
||||
its preferred representation, e.g. conversion to string or mapped
|
||||
@@ -797,14 +904,20 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
prop.configurable = false;
|
||||
prop.enumerable = false;
|
||||
prop.get = function(){
|
||||
/**
|
||||
This getter proxy reads its value from the appropriate pointer
|
||||
address in the heap. It knows where and how much to read based on
|
||||
this.pointer, si.offset, and si.sizeof.
|
||||
*/
|
||||
if(dbg.getter){
|
||||
log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph),
|
||||
log("debug.getter:",f.cache.getters[sigGlyph],"for", sigIR(sigGlyph),
|
||||
xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof);
|
||||
}
|
||||
let rc = (
|
||||
new DataView(heap().buffer, Number(this.pointer) + si.offset, si.sizeof)
|
||||
)[f._.getters[sigGlyph]](0, isLittleEndian);
|
||||
if(getterProxy) rc = getterProxy.apply(this,[rc,key]);
|
||||
)[f.cache.getters[sigGlyph]](0, isLittleEndian);
|
||||
|
||||
if(getterProxy) rc = getterProxy.apply(this,[key,rc]);
|
||||
if(dbg.getter) log("debug.getter:",xPropName,"result =",rc);
|
||||
return rc;
|
||||
};
|
||||
@@ -813,28 +926,33 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
}else{
|
||||
const setterProxy = memberSetterProxy(si);
|
||||
prop.set = function(v){
|
||||
/**
|
||||
The converse of prop.get(), this encodes v into the appropriate
|
||||
spot in the WASM heap.
|
||||
*/
|
||||
if(dbg.setter){
|
||||
log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph),
|
||||
log("debug.setter:",f.cache.setters[sigGlyph],"for", sigIR(sigGlyph),
|
||||
xPropName,'@', this.pointer,'+',si.offset,'sz',si.sizeof, v);
|
||||
}
|
||||
if(!this.pointer){
|
||||
toss("Cannot set native property on a disposed struct instance.");
|
||||
toss("Cannot set native property on a disposed",
|
||||
this.structSame,"instance.");
|
||||
}
|
||||
if( setterProxy ) v = setterProxy.apply(this,[v]);
|
||||
if( setterProxy ) v = setterProxy.apply(this,[key,v]);
|
||||
if( null===v || undefined===v ) v = __NullPtr;
|
||||
else while( isPtrSig(si.signature) && !__isPtr(v) ){
|
||||
else if( isPtrSig(si.signature) && !__isPtr(v) ){
|
||||
if(isAutoPtrSig(si.signature) && (v instanceof StructType)){
|
||||
// It's a struct instance: let's store its pointer value!
|
||||
// It's a struct instance: store its pointer value
|
||||
v = v.pointer || __NullPtr;
|
||||
if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v);
|
||||
break;
|
||||
}else{
|
||||
toss("Invalid value for pointer-type",xPropName+'.');
|
||||
}
|
||||
toss("Invalid value for pointer-type",xPropName+'.');
|
||||
}
|
||||
(
|
||||
new DataView(heap().buffer, Number(this.pointer) + si.offset,
|
||||
si.sizeof)
|
||||
)[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian);
|
||||
)[f.cache.setters[sigGlyph]](0, f.cache.sw[sigGlyph](v), isLittleEndian);
|
||||
};
|
||||
}
|
||||
Object.defineProperty(ctor.prototype, key, prop);
|
||||
@@ -863,25 +981,59 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
StructCtor is the eventual return value of this function. We
|
||||
need to populate this early on so that we can do some trickery
|
||||
in feeding it through recursion.
|
||||
|
||||
Uses:
|
||||
|
||||
// heap-allocated:
|
||||
const x = new StructCtor;
|
||||
// externally-managed memory:
|
||||
const y = new StructCtor( aPtrToACompatibleCStruct );
|
||||
|
||||
or, more recently:
|
||||
|
||||
const z = new StructCtor({
|
||||
extraBytes: [int=0] extra bytes to allocate after the struct
|
||||
|
||||
wrap: [aPtrToACompatibleCStruct=undefined]. If provided, this
|
||||
instance waps, but does not (by default) own the memory, else
|
||||
a new instance is allocated from the WASM heap.
|
||||
|
||||
ownsPointer: true if this object takes over ownership of
|
||||
wrap.
|
||||
|
||||
zeroOnDispose: [bool=StructCtor.structInfo.zeroOnDispose]
|
||||
|
||||
autoCalcSizeOffset: [bool=false] Automatically calculate
|
||||
sizeof an offset. This is fine for pure-JS structs (which
|
||||
probably aren't useful beyond testing of Jaccwabyt) but it's
|
||||
dangerous to use with actual WASM objects because we cannot
|
||||
be guaranteed to have the same memory layout as an ostensibly
|
||||
matching C struct. This applies recursively to all children
|
||||
of the struct description.
|
||||
|
||||
// TODO? Per-instance overrides of the struct-level flags?
|
||||
|
||||
get: (k,v)=>v,
|
||||
set: (k,v)=>v,
|
||||
adaptGet: string,
|
||||
adaptSet: string
|
||||
|
||||
// That wouldn't fit really well right now, apparently.
|
||||
});
|
||||
|
||||
*/
|
||||
const StructCtor = function StructCtor(externalMemory){
|
||||
externalMemory = __asPtrType(externalMemory);
|
||||
//console.warn("externalMemory",externalMemory,arguments[0]);
|
||||
const StructCtor = function StructCtor(arg){
|
||||
//console.warn("opt",opt,arguments[0]);
|
||||
if(!(this instanceof StructCtor)){
|
||||
toss("The",structName,"constructor may only be called via 'new'.");
|
||||
}else if(arguments.length){
|
||||
if( !__isPtr(externalMemory) ){
|
||||
toss("Invalid pointer value",arguments[0],"for",structName,"constructor.");
|
||||
}
|
||||
__allocStruct(StructCtor, this, externalMemory);
|
||||
}else{
|
||||
__allocStruct(StructCtor, this);
|
||||
}
|
||||
__allocStruct(StructCtor, this, ...arguments);
|
||||
};
|
||||
const self = this;
|
||||
/**
|
||||
"Convert" struct description x to a struct description, if
|
||||
needed.
|
||||
needed. This expands adaptStruct() mappings and transforms
|
||||
{memberName:signatureString} signature syntax to object form.
|
||||
*/
|
||||
const ads = (x)=>{
|
||||
//console.warn("looksLikeASig(",x,") =",looksLikeASig(x));
|
||||
@@ -902,6 +1054,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
structName ??= opt.structName;
|
||||
if( !structName ) toss("One of 'name' or 'structName' are required.");
|
||||
if( si.adapt ){
|
||||
/* Install adaptGet(), adaptSet(), and adaptStruct() proxies. */
|
||||
Object.keys(si.adapt.struct||{}).forEach((k)=>{
|
||||
__adaptStruct.call(StructBinderImpl, k, si.adapt.struct[k]);
|
||||
});
|
||||
@@ -924,21 +1077,32 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
memberKeys: __structMemberKeys,
|
||||
//methodInfoForKey: rop(function(mKey){/*???*/}),
|
||||
structInfo: rop(si),
|
||||
structName: rop(structName)
|
||||
structName: rop(structName),
|
||||
ptrAdd: rop(__ptrAdd)
|
||||
});
|
||||
StructCtor.prototype = new StructType(structName, si, rop);
|
||||
Object.defineProperties(StructCtor.prototype,{
|
||||
debugFlags: debugFlags,
|
||||
constructor: rop(StructCtor)
|
||||
/*if we assign StructCtor.prototype and don't do
|
||||
this then StructCtor!==instance.constructor*/
|
||||
this then StructCtor!==instance.constructor*/,
|
||||
ptrAdd: rop(__ptrAddSelf)
|
||||
});
|
||||
|
||||
|
||||
let lastMember = false;
|
||||
let offset = 0;
|
||||
const autoCalc = !!si.autoCalcSizeOffset;
|
||||
//console.warn(structName,"si =",si);
|
||||
si.offset ??= 0;
|
||||
if( !autoCalc ){
|
||||
if( !si.sizeof ){
|
||||
toss(structName,"description is missing its sizeof property.");
|
||||
}
|
||||
/*if( undefined===si.offset ){
|
||||
toss(structName,"description is missing its offset property.");
|
||||
}*/
|
||||
si.offset ??= 0;
|
||||
}else{
|
||||
si.offset ??= 0;
|
||||
}
|
||||
Object.keys(si.members || {}).forEach((k)=>{
|
||||
// Sanity checks of sizeof/offset info...
|
||||
let m = ads(si.members[k]);
|
||||
@@ -951,11 +1115,23 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
}
|
||||
}
|
||||
if( undefined===m.offset ){
|
||||
m.offset = offset;
|
||||
if( autoCalc ) m.offset = offset;
|
||||
else{
|
||||
toss(sPropName(structName,k),"is missing its offset.",
|
||||
JSON.stringify(m));
|
||||
}
|
||||
/* A missing offset on the initial child is okay (it's always
|
||||
zero), but we don't know for sure that the members are
|
||||
their natural order, so we don't know, at this point, which
|
||||
one is "first". */
|
||||
}
|
||||
si.members[k] = m;
|
||||
si.members[k] = m /* in case ads() resolved it to something else */;
|
||||
if(!lastMember || lastMember.offset < m.offset) lastMember = m;
|
||||
makeMemberWrapper.call(self, StructCtor, k, m)
|
||||
const oldAutoCalc = !!m.autoCalc;
|
||||
if( autoCalc ) m.autoCalcSizeOffset = true;
|
||||
makeMemberWrapper.call(self, StructCtor, k, m);
|
||||
if( oldAutoCalc ) m.autoCalcSizeOffset = true;
|
||||
else delete m.autoCalcSizeOffset;
|
||||
offset += m.sizeof;
|
||||
//console.warn("offset",sPropName(structName,k),offset);
|
||||
});
|
||||
@@ -979,12 +1155,15 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
toss(structName,"offset is not aligned. offset="+si.offset);
|
||||
}
|
||||
}
|
||||
|
||||
if( si.sizeof < offset ){
|
||||
console.warn("Suspect struct description:",si,"offset =",offset);
|
||||
toss("Mismatch in the calculated vs. the provided sizeof/offset info.",
|
||||
"Expected sizeof",offset,"but got",si.sizeof,"for",si);
|
||||
/* It is legal for the native struct to be larger, so long as
|
||||
we're pointing to all the right offsets for the members
|
||||
exposed here. */
|
||||
}
|
||||
delete si.autoCalcSizeOffset;
|
||||
return StructCtor;
|
||||
}/*StructBinderImpl*/;
|
||||
|
||||
@@ -999,6 +1178,7 @@ globalThis.Jaccwabyt = function StructBinderFactory(config){
|
||||
StructBinder.adaptGet = __adaptGet;
|
||||
StructBinder.adaptSet = __adaptSet;
|
||||
StructBinder.adaptStruct = __adaptStruct;
|
||||
StructBinder.ptrAdd = __ptrAdd;
|
||||
if(!StructBinder.debugFlags){
|
||||
StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags);
|
||||
}
|
||||
|
||||
@@ -1027,7 +1027,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
|
||||
let ptr = k1.pointer;
|
||||
k1.dispose();
|
||||
T.assert(undefined === k1.pointer).
|
||||
mustThrowMatching(()=>{k1.$pP=1}, /disposed struct instance/);
|
||||
mustThrowMatching(()=>{k1.$pP=1}, /disposed/);
|
||||
}finally{
|
||||
k1.dispose();
|
||||
k2.dispose();
|
||||
@@ -2907,11 +2907,12 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
|
||||
]);
|
||||
T.assert(3 === db.selectValue('select count(*) from kvvfs'));
|
||||
db.close();
|
||||
db = undefined;
|
||||
db = new JDb(filename);
|
||||
db.exec('insert into kvvfs(a) values(4),(5),(6)');
|
||||
T.assert(6 === db.selectValue('select count(*) from kvvfs'));
|
||||
}finally{
|
||||
db.close();
|
||||
if( db ) db.close();
|
||||
}
|
||||
}
|
||||
}/*kvvfs sanity checks*/)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
C Test\scases\sfor\sthe\sfix\sin\sthe\sprior\scheck-in.
|
||||
D 2025-11-20T22:46:27.556
|
||||
C Move\sthe\sJS\spieces\sof\skvvfs\sinto\stheir\sown\sfile\sto\sfacilitate\spending\sfeature\sexperimentation.
|
||||
D 2025-11-21T10:49:32.462
|
||||
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
@@ -578,7 +578,7 @@ F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009e
|
||||
F ext/session/sqlite3session.c b3de195ce668cace9b324599bf6255a70290cbfb5451e826e946f3aee6e64c54
|
||||
F ext/session/sqlite3session.h 7404723606074fcb2afdc6b72c206072cdb2b7d8ba097ca1559174a80bc26f7a
|
||||
F ext/session/test_session.c 8766b5973a6323934cb51248f621c3dc87ad2a98f023c3cc280d79e7d78d36fb
|
||||
F ext/wasm/GNUmakefile 01f5e1bc688911b0c0accb0b152dccb19818cb301a90ba9772e3b329a7f625b0
|
||||
F ext/wasm/GNUmakefile bff7f432a65bc5152ae5aaf19f480dd16fa7fdd11a451773b7dbcac544c703b3
|
||||
F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a
|
||||
F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259
|
||||
F ext/wasm/SQLTester/GNUmakefile e0794f676d55819951bbfae45cc5e8d7818dc460492dc317ce7f0d2eca15caff
|
||||
@@ -593,9 +593,9 @@ F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e
|
||||
F ext/wasm/api/post-js-footer.js a50c1a2c4d008aede7b2aa1f18891a7ee71437c2f415b8aeb3db237ddce2935b
|
||||
F ext/wasm/api/post-js-header.js d24bd0d065f3489c8b78ddf3ead6321e5d047187a162cd503c41700e03dd1f06
|
||||
F ext/wasm/api/pre-js.c-pp.js ad2546290e0c8ce5ca2081bff6e85cc25eeb904a3303921f1184290a7ff1b32f
|
||||
F ext/wasm/api/sqlite3-api-glue.c-pp.js 6ff61b7a7a8fb4367ebb2262d9f95a4b8c43eb6964890af99fd322f6cb1b4f00
|
||||
F ext/wasm/api/sqlite3-api-oo1.c-pp.js 31dbfd470c91ffd96d77399b749bab6b69e3ba9074188833f97ac13f087cf07b
|
||||
F ext/wasm/api/sqlite3-api-prologue.js a7cab3e8f78d5a93d7173657677fef18265f48d4e0b1e1755740f734a69cae6d
|
||||
F ext/wasm/api/sqlite3-api-glue.c-pp.js 9eaed1801be392f6687aa7da8e3a5a41d03de19993d8fe62ee6c52617eab4985
|
||||
F ext/wasm/api/sqlite3-api-oo1.c-pp.js 8ce38bd4b22aa2b0311c7a8e87e748e06213766fe2141de7574672d103ece255
|
||||
F ext/wasm/api/sqlite3-api-prologue.js 7004b569624765c5132984bfecee2305bef928a6adf44e0202dacc9cbc5c8e2a
|
||||
F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667430f9d45167569633ffc7567938
|
||||
F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
|
||||
F ext/wasm/api/sqlite3-opfs-async-proxy.js 9654b565b346dc609b75d15337f20acfa7af7d9d558da1afeb9b6d8eaa404966
|
||||
@@ -627,7 +627,7 @@ F ext/wasm/fiddle/fiddle.js 84fd75967e0af8b69d3dd849818342227d0f81d13db92e0dcbc6
|
||||
F ext/wasm/fiddle/index.html a27b8127ef9ecf19612da93b2a6a73bdb3777b5c56b5450bb7200a94bc108ff9
|
||||
F ext/wasm/index-dist.html db23748044e286773f2768eec287669501703b5d5f72755e8db73607dc54d290
|
||||
F ext/wasm/index.html 54e27db740695ab2cb296e02d42c4c66b3f11b65797340d19fa6590f5b287da1
|
||||
F ext/wasm/jaccwabyt/jaccwabyt.js 1e734c624205cdf621f322972dfb0fc8013d573a5882f57492a6830e5ec23e17
|
||||
F ext/wasm/jaccwabyt/jaccwabyt.js 236464b7c8e2d2540a296ba049b3e812e2697ef8439ac96f0d20716e46ba6809
|
||||
F ext/wasm/jaccwabyt/jaccwabyt.md 167fc0b624c9bc2c477846e336de9403842d81b1a24fc4d3b24317cb9eba734f
|
||||
F ext/wasm/mkdist.sh 64d53f469c823ed311f6696f69cec9093f745e467334b34f5ceabdf9de3c5b28 x
|
||||
F ext/wasm/mkwasmbuilds.c ef42e404236dd98cedb6ecea47b6d2474e3c593633ce6d992d316289dfc442b6
|
||||
@@ -646,7 +646,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
|
||||
F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
|
||||
F ext/wasm/tester1-worker.c-pp.html 0e432ec2c0d99cd470484337066e8d27e7aee4641d97115338f7d962bf7b081a
|
||||
F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb
|
||||
F ext/wasm/tester1.c-pp.js 2c255093205a0dac9dae7475030665c2c9d6dccc857de68ee7daf49aa82e6de8
|
||||
F ext/wasm/tester1.c-pp.js 015b4133cc3a5fb41d6236a6b39d23d996cc2d61a4877acde31a1f69574d4ce3
|
||||
F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e
|
||||
F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88
|
||||
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
|
||||
@@ -2175,8 +2175,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
|
||||
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
|
||||
F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
P 8896185ae0b0af8918aa8ce449f18759e6ae60358afbac1019397bae307b15d5
|
||||
R 254d017f2dd051b92cbdd62f2ef390cd
|
||||
U drh
|
||||
Z 1247e56565ac3eb588c6a5672754d68c
|
||||
P 4d41bee75eda51251121c8e3903f47941116e5182238a03f41a593c47efb6fcf
|
||||
R 89e12ac8833b73cf56a70154881e6f51
|
||||
U stephan
|
||||
Z 653a6aeee27ed8b2d1b86bc8d2dfe977
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
4d41bee75eda51251121c8e3903f47941116e5182238a03f41a593c47efb6fcf
|
||||
3c40614285449df259a3444e36f888cfb5e782ea58287914f97f496ea61e9e9f
|
||||
|
||||
Reference in New Issue
Block a user