Skip to content

Commit 0c070c4

Browse files
authored
Use JS class for FSStream, FSNode and LazyUint8Array. NFC (#21406)
Converting each of these to classes led to code size savings.
1 parent 72d980b commit 0c070c4

15 files changed

+183
-194
lines changed

src/library_fs.js

Lines changed: 169 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -39,55 +39,7 @@ if (!Module['noFSInit'] && !FS.init.initialized)
3939
FS.ignorePermissions = false;
4040
`)
4141
addAtExit('FS.quit();');
42-
// We must statically create FS.FSNode here so that it is created in a manner
43-
// that is visible to Closure compiler. That lets us use type annotations for
44-
// Closure to the "this" pointer in various node creation functions.
4542
return `
46-
var FSNode = /** @constructor */ function(parent, name, mode, rdev) {
47-
if (!parent) {
48-
parent = this; // root node sets parent to itself
49-
}
50-
this.parent = parent;
51-
this.mount = parent.mount;
52-
this.mounted = null;
53-
this.id = FS.nextInode++;
54-
this.name = name;
55-
this.mode = mode;
56-
this.node_ops = {};
57-
this.stream_ops = {};
58-
this.rdev = rdev;
59-
};
60-
var readMode = 292/*{{{ cDefs.S_IRUGO }}}*/ | 73/*{{{ cDefs.S_IXUGO }}}*/;
61-
var writeMode = 146/*{{{ cDefs.S_IWUGO }}}*/;
62-
Object.defineProperties(FSNode.prototype, {
63-
read: {
64-
get: /** @this{FSNode} */function() {
65-
return (this.mode & readMode) === readMode;
66-
},
67-
set: /** @this{FSNode} */function(val) {
68-
val ? this.mode |= readMode : this.mode &= ~readMode;
69-
}
70-
},
71-
write: {
72-
get: /** @this{FSNode} */function() {
73-
return (this.mode & writeMode) === writeMode;
74-
},
75-
set: /** @this{FSNode} */function(val) {
76-
val ? this.mode |= writeMode : this.mode &= ~writeMode;
77-
}
78-
},
79-
isFolder: {
80-
get: /** @this{FSNode} */function() {
81-
return FS.isDir(this.mode);
82-
}
83-
},
84-
isDevice: {
85-
get: /** @this{FSNode} */function() {
86-
return FS.isChrdev(this.mode);
87-
}
88-
}
89-
});
90-
FS.FSNode = FSNode;
9143
FS.createPreloadedFile = FS_createPreloadedFile;
9244
FS.staticInit();` +
9345
// Get module methods from settings
@@ -145,6 +97,79 @@ FS.staticInit();` +
14597
}
14698
},
14799

100+
FSStream: class {
101+
constructor() {
102+
this.shared = {};
103+
#if USE_CLOSURE_COMPILER
104+
this.node = null;
105+
this.flags = 0;
106+
#endif
107+
}
108+
get object() {
109+
return this.node;
110+
}
111+
set object(val) {
112+
this.node = val;
113+
}
114+
get isRead() {
115+
return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_WRONLY }}};
116+
}
117+
get isWrite() {
118+
return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_RDONLY }}};
119+
}
120+
get isAppend() {
121+
return (this.flags & {{{ cDefs.O_APPEND }}});
122+
}
123+
get flags() {
124+
return this.shared.flags;
125+
}
126+
set flags(val) {
127+
this.shared.flags = val;
128+
}
129+
get position() {
130+
return this.shared.position;
131+
}
132+
set position(val) {
133+
this.shared.position = val;
134+
}
135+
},
136+
FSNode: class {
137+
constructor(parent, name, mode, rdev) {
138+
if (!parent) {
139+
parent = this; // root node sets parent to itself
140+
}
141+
this.parent = parent;
142+
this.mount = parent.mount;
143+
this.mounted = null;
144+
this.id = FS.nextInode++;
145+
this.name = name;
146+
this.mode = mode;
147+
this.node_ops = {};
148+
this.stream_ops = {};
149+
this.rdev = rdev;
150+
this.readMode = 292/*{{{ cDefs.S_IRUGO }}}*/ | 73/*{{{ cDefs.S_IXUGO }}}*/;
151+
this.writeMode = 146/*{{{ cDefs.S_IWUGO }}}*/;
152+
}
153+
get read() {
154+
return (this.mode & this.readMode) === this.readMode;
155+
}
156+
set read(val) {
157+
val ? this.mode |= this.readMode : this.mode &= ~this.readMode;
158+
}
159+
get write() {
160+
return (this.mode & this.writeMode) === this.writeMode;
161+
}
162+
set write(val) {
163+
val ? this.mode |= this.writeMode : this.mode &= ~this.writeMode;
164+
}
165+
get isFolder() {
166+
return FS.isDir(this.mode);
167+
}
168+
get isDevice() {
169+
return FS.isChrdev(this.mode);
170+
}
171+
},
172+
148173
//
149174
// paths
150175
//
@@ -421,44 +446,7 @@ FS.staticInit();` +
421446
// object isn't directly passed in. not possible until
422447
// SOCKFS is completed.
423448
createStream(stream, fd = -1) {
424-
if (!FS.FSStream) {
425-
FS.FSStream = /** @constructor */ function() {
426-
this.shared = { };
427-
};
428-
FS.FSStream.prototype = {};
429-
Object.defineProperties(FS.FSStream.prototype, {
430-
object: {
431-
/** @this {FS.FSStream} */
432-
get() { return this.node; },
433-
/** @this {FS.FSStream} */
434-
set(val) { this.node = val; }
435-
},
436-
isRead: {
437-
/** @this {FS.FSStream} */
438-
get() { return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_WRONLY }}}; }
439-
},
440-
isWrite: {
441-
/** @this {FS.FSStream} */
442-
get() { return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_RDONLY }}}; }
443-
},
444-
isAppend: {
445-
/** @this {FS.FSStream} */
446-
get() { return (this.flags & {{{ cDefs.O_APPEND }}}); }
447-
},
448-
flags: {
449-
/** @this {FS.FSStream} */
450-
get() { return this.shared.flags; },
451-
/** @this {FS.FSStream} */
452-
set(val) { this.shared.flags = val; },
453-
},
454-
position : {
455-
/** @this {FS.FSStream} */
456-
get() { return this.shared.position; },
457-
/** @this {FS.FSStream} */
458-
set(val) { this.shared.position = val; },
459-
},
460-
});
461-
}
449+
462450
// clone it, so we can return an instance of FSStream
463451
stream = Object.assign(new FS.FSStream(), stream);
464452
if (fd == -1) {
@@ -1657,111 +1645,112 @@ FS.staticInit();` +
16571645
// XHR, which is not possible in browsers except in a web worker! Use preloading,
16581646
// either --preload-file in emcc or FS.createPreloadedFile
16591647
createLazyFile(parent, name, url, canRead, canWrite) {
1660-
// Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse.
1661-
/** @constructor */
1662-
function LazyUint8Array() {
1663-
this.lengthKnown = false;
1664-
this.chunks = []; // Loaded chunks. Index is the chunk number
1665-
}
1666-
LazyUint8Array.prototype.get = /** @this{Object} */ function LazyUint8Array_get(idx) {
1667-
if (idx > this.length-1 || idx < 0) {
1668-
return undefined;
1669-
}
1670-
var chunkOffset = idx % this.chunkSize;
1671-
var chunkNum = (idx / this.chunkSize)|0;
1672-
return this.getter(chunkNum)[chunkOffset];
1673-
};
1674-
LazyUint8Array.prototype.setDataGetter = function LazyUint8Array_setDataGetter(getter) {
1675-
this.getter = getter;
1676-
};
1677-
LazyUint8Array.prototype.cacheLength = function LazyUint8Array_cacheLength() {
1678-
// Find length
1679-
var xhr = new XMLHttpRequest();
1680-
xhr.open('HEAD', url, false);
1681-
xhr.send(null);
1682-
if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status);
1683-
var datalength = Number(xhr.getResponseHeader("Content-length"));
1684-
var header;
1685-
var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes";
1686-
var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip";
1687-
1688-
#if SMALL_XHR_CHUNKS
1689-
var chunkSize = 1024; // Chunk size in bytes
1690-
#else
1691-
var chunkSize = 1024*1024; // Chunk size in bytes
1648+
// Lazy chunked Uint8Array (implements get and length from Uint8Array).
1649+
// Actual getting is abstracted away for eventual reuse.
1650+
class LazyUint8Array {
1651+
constructor() {
1652+
this.lengthKnown = false;
1653+
this.chunks = []; // Loaded chunks. Index is the chunk number
1654+
#if USE_CLOSURE_COMPILER
1655+
this.getter = undefined;
1656+
this._length = 0;
1657+
this._chunkSize = 0;
16921658
#endif
1693-
1694-
if (!hasByteServing) chunkSize = datalength;
1695-
1696-
// Function to get a range from the remote URL.
1697-
var doXHR = (from, to) => {
1698-
if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!");
1699-
if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!");
1700-
1701-
// TODO: Use mozResponseArrayBuffer, responseStream, etc. if available.
1659+
}
1660+
get(idx) {
1661+
if (idx > this.length-1 || idx < 0) {
1662+
return undefined;
1663+
}
1664+
var chunkOffset = idx % this.chunkSize;
1665+
var chunkNum = (idx / this.chunkSize)|0;
1666+
return this.getter(chunkNum)[chunkOffset];
1667+
}
1668+
setDataGetter(getter) {
1669+
this.getter = getter;
1670+
}
1671+
cacheLength() {
1672+
// Find length
17021673
var xhr = new XMLHttpRequest();
1703-
xhr.open('GET', url, false);
1704-
if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to);
1674+
xhr.open('HEAD', url, false);
1675+
xhr.send(null);
1676+
if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status);
1677+
var datalength = Number(xhr.getResponseHeader("Content-length"));
1678+
var header;
1679+
var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes";
1680+
var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip";
1681+
1682+
#if SMALL_XHR_CHUNKS
1683+
var chunkSize = 1024; // Chunk size in bytes
1684+
#else
1685+
var chunkSize = 1024*1024; // Chunk size in bytes
1686+
#endif
1687+
1688+
if (!hasByteServing) chunkSize = datalength;
1689+
1690+
// Function to get a range from the remote URL.
1691+
var doXHR = (from, to) => {
1692+
if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!");
1693+
if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!");
1694+
1695+
// TODO: Use mozResponseArrayBuffer, responseStream, etc. if available.
1696+
var xhr = new XMLHttpRequest();
1697+
xhr.open('GET', url, false);
1698+
if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to);
1699+
1700+
// Some hints to the browser that we want binary data.
1701+
xhr.responseType = 'arraybuffer';
1702+
if (xhr.overrideMimeType) {
1703+
xhr.overrideMimeType('text/plain; charset=x-user-defined');
1704+
}
17051705

1706-
// Some hints to the browser that we want binary data.
1707-
xhr.responseType = 'arraybuffer';
1708-
if (xhr.overrideMimeType) {
1709-
xhr.overrideMimeType('text/plain; charset=x-user-defined');
1706+
xhr.send(null);
1707+
if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status);
1708+
if (xhr.response !== undefined) {
1709+
return new Uint8Array(/** @type{Array<number>} */(xhr.response || []));
1710+
}
1711+
return intArrayFromString(xhr.responseText || '', true);
1712+
};
1713+
var lazyArray = this;
1714+
lazyArray.setDataGetter((chunkNum) => {
1715+
var start = chunkNum * chunkSize;
1716+
var end = (chunkNum+1) * chunkSize - 1; // including this byte
1717+
end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block
1718+
if (typeof lazyArray.chunks[chunkNum] == 'undefined') {
1719+
lazyArray.chunks[chunkNum] = doXHR(start, end);
1720+
}
1721+
if (typeof lazyArray.chunks[chunkNum] == 'undefined') throw new Error('doXHR failed!');
1722+
return lazyArray.chunks[chunkNum];
1723+
});
1724+
1725+
if (usesGzip || !datalength) {
1726+
// if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length
1727+
chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file
1728+
datalength = this.getter(0).length;
1729+
chunkSize = datalength;
1730+
out("LazyFiles on gzip forces download of the whole file when length is accessed");
17101731
}
17111732

1712-
xhr.send(null);
1713-
if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status);
1714-
if (xhr.response !== undefined) {
1715-
return new Uint8Array(/** @type{Array<number>} */(xhr.response || []));
1733+
this._length = datalength;
1734+
this._chunkSize = chunkSize;
1735+
this.lengthKnown = true;
1736+
}
1737+
get length() {
1738+
if (!this.lengthKnown) {
1739+
this.cacheLength();
17161740
}
1717-
return intArrayFromString(xhr.responseText || '', true);
1718-
};
1719-
var lazyArray = this;
1720-
lazyArray.setDataGetter((chunkNum) => {
1721-
var start = chunkNum * chunkSize;
1722-
var end = (chunkNum+1) * chunkSize - 1; // including this byte
1723-
end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block
1724-
if (typeof lazyArray.chunks[chunkNum] == 'undefined') {
1725-
lazyArray.chunks[chunkNum] = doXHR(start, end);
1741+
return this._length;
1742+
}
1743+
get chunkSize() {
1744+
if (!this.lengthKnown) {
1745+
this.cacheLength();
17261746
}
1727-
if (typeof lazyArray.chunks[chunkNum] == 'undefined') throw new Error('doXHR failed!');
1728-
return lazyArray.chunks[chunkNum];
1729-
});
1730-
1731-
if (usesGzip || !datalength) {
1732-
// if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length
1733-
chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file
1734-
datalength = this.getter(0).length;
1735-
chunkSize = datalength;
1736-
out("LazyFiles on gzip forces download of the whole file when length is accessed");
1747+
return this._chunkSize;
17371748
}
1749+
}
17381750

1739-
this._length = datalength;
1740-
this._chunkSize = chunkSize;
1741-
this.lengthKnown = true;
1742-
};
17431751
if (typeof XMLHttpRequest != 'undefined') {
17441752
if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc';
17451753
var lazyArray = new LazyUint8Array();
1746-
Object.defineProperties(lazyArray, {
1747-
length: {
1748-
get: /** @this{Object} */ function() {
1749-
if (!this.lengthKnown) {
1750-
this.cacheLength();
1751-
}
1752-
return this._length;
1753-
}
1754-
},
1755-
chunkSize: {
1756-
get: /** @this{Object} */ function() {
1757-
if (!this.lengthKnown) {
1758-
this.cacheLength();
1759-
}
1760-
return this._chunkSize;
1761-
}
1762-
}
1763-
});
1764-
17651754
var properties = { isDevice: false, contents: lazyArray };
17661755
} else {
17671756
var properties = { isDevice: false, url: url };
@@ -1780,7 +1769,7 @@ FS.staticInit();` +
17801769
// Add a function that defers querying the file size until it is asked the first time.
17811770
Object.defineProperties(node, {
17821771
usedBytes: {
1783-
get: /** @this {FSNode} */ function() { return this.contents.length; }
1772+
get: function() { return this.contents.length; }
17841773
}
17851774
});
17861775
// override each stream op with one that tries to force load the lazy file first
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
9953
1+
9887

0 commit comments

Comments
 (0)