Skip to content

Commit 6aba817

Browse files
authored
Merge pull request emscripten-forge#25 from martinRenou/refactor_wasm_init
Refactor wasm initialization
2 parents 92208d2 + c4c381d commit 6aba817

File tree

11 files changed

+277
-3668
lines changed

11 files changed

+277
-3668
lines changed

.eslintrc.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ module.exports = {
22
env: {
33
browser: true,
44
es6: true,
5-
commonjs: true,
6-
jest: true
5+
commonjs: true
76
},
87
root: true,
98
extends: [
@@ -78,4 +77,4 @@ module.exports = {
7877
}
7978
]
8079
}
81-
};
80+
};

.github/workflows/main.yml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
- name: Set up Node.js
3636
uses: actions/setup-node@v2
3737
with:
38-
node-version: '16'
38+
node-version: '16'
3939

4040
- name: Install dependencies
4141
run: yarn install
@@ -51,9 +51,3 @@ jobs:
5151
micromamba activate untarjs
5252
5353
yarn run eslint
54-
55-
- name: Run test
56-
run: |
57-
micromamba activate untarjs
58-
59-
yarn run test

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ This package has 2 methods:
1818

1919
The example of using:
2020
```sh
21-
import untarjs from "@emscripten-forge/untarjs";
21+
import { initUntarJS } from "@emscripten-forge/untarjs";
22+
23+
const untarjs = await initUntarJS();
2224

2325
const condaPackageUrl = 'https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2';
2426
untarjs.extract(condaPackageUrl).then((files)=>{

jest.config.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
"prettier": "prettier --list-different --write \"src/**/*.ts\"",
1313
"prettier:check": "prettier --check \"src/**/*.ts\"",
1414
"eslint": "eslint --ext .ts --fix .",
15-
"eslint:check": "eslint --ext .ts.",
16-
"test": "jest"
15+
"eslint:check": "eslint --ext .ts."
1716
},
1817
"files": [
1918
"lib/"
@@ -45,20 +44,16 @@
4544
},
4645
"devDependencies": {
4746
"@eslint/js": "^9.13.0",
48-
"@types/jest": "^29.5.14",
4947
"@types/node": "^22.8.1",
5048
"@typescript-eslint/eslint-plugin": "~6.13.2",
5149
"@typescript-eslint/parser": "~6.13.2",
5250
"eslint": "~8.55.0",
5351
"eslint-config-prettier": "~9.1.0",
54-
"eslint-plugin-jest": "~27.6.0",
5552
"eslint-plugin-prettier": "~5.0.1",
5653
"eslint-plugin-react": "~7.33.2",
5754
"globals": "^15.11.0",
58-
"jest": "^29.7.0",
5955
"prettier": "^3.3.3",
6056
"rimraf": "^3.0.2",
61-
"ts-jest": "^29.2.5",
6257
"typescript": "^5",
6358
"typescript-eslint": "^8.12.2"
6459
}

src/helper.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
import unpack, { IWasmModule } from './unpack';
22
import unpackWasm from './unpack.wasm';
33

4-
const initializeWasm = async (): Promise<IWasmModule | undefined> => {
5-
try {
6-
const wasmModule: IWasmModule = await unpack({
7-
locateFile(path: string) {
8-
if (path.endsWith('.wasm')) {
9-
return unpackWasm;
10-
}
11-
return path;
4+
const initializeWasm = async (): Promise<IWasmModule> => {
5+
const wasmModule: IWasmModule = await unpack({
6+
locateFile(path: string) {
7+
if (path.endsWith('.wasm')) {
8+
return unpackWasm;
129
}
13-
});
10+
return path;
11+
}
12+
});
1413

15-
return wasmModule;
16-
} catch (err) {
17-
console.error('Error initializing the WASM module:', err);
18-
return undefined;
19-
}
14+
return wasmModule;
2015
};
16+
2117
export default initializeWasm;

src/index.ts

Lines changed: 114 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import initializeWasm from './helper';
2-
import { IFileData } from './types';
3-
import { IWasmModule } from './unpack';
2+
import { IFileData, IUnpackJSAPI } from './types';
43

54
const fetchByteArray = async (url: string): Promise<Uint8Array> => {
65
const response = await fetch(url);
@@ -11,138 +10,125 @@ const fetchByteArray = async (url: string): Promise<Uint8Array> => {
1110
return new Uint8Array(arrayBuffer);
1211
};
1312

14-
const init = async (): Promise<IWasmModule | null> => {
15-
try {
16-
const wasmModule = await initializeWasm();
17-
return wasmModule as IWasmModule;
18-
} catch (error) {
19-
console.error('Error initializing WASM module:', error);
20-
return null;
21-
}
22-
};
23-
24-
export const extractData = async (data: Uint8Array): Promise<IFileData[]> => {
25-
const wasmModule = await init();
26-
if (!wasmModule) {
27-
console.error('WASM module not initialized.');
28-
return [];
29-
}
30-
31-
/**Since WebAssembly, memory is accessed using pointers
32-
and the first parameter of extract_archive method from unpack.c, which is Uint8Array of file data, should be a pointer
33-
so we have to allocate memory for file data
34-
**/
35-
const inputPtr = wasmModule._malloc(data.length);
36-
wasmModule.HEAPU8.set(data, inputPtr);
37-
38-
// fileCountPtr is the pointer to 4 bytes of memory in WebAssembly's heap that holds fileCount value from the ExtractedArchive structure in unpack.c.
39-
const fileCountPtr = wasmModule._malloc(4);
40-
41-
try {
42-
const resultPtr = wasmModule._extract_archive(
43-
inputPtr,
44-
data.length,
45-
fileCountPtr
46-
);
47-
48-
/**
49-
* Since extract_archive returns a pointer that refers to an instance of the ExtractedArchive in unpack.c
50-
typedef struct {
51-
FileData* files;
52-
size_t fileCount;
53-
int status;
54-
char* error_message;
55-
} ExtractedArchive;
56-
57-
its fields are laid out in memory sequentially. Based on this and types each field will take 4 bytes:
58-
59-
4 bytes 4 bytes 4 bytes 4 bytes
60-
---------------|---------------|---------------|---------------
61-
files fileCount status error_message
62-
63-
`resultPtr` points to the beginning of the ExtractedArchive structure in WebAssembly memory
64-
and in order to get pointer of statusPtr we need to calculate it as: 0(offset of file pointer) + 4 (offset of fileCount) + 4 (offset for status)
65-
'status' field and pointer of `error_message` are 32-bit signed integer
66-
*/
67-
const statusPtr = wasmModule.getValue(resultPtr + 8, 'i32');
68-
const errorMessagePtr = wasmModule.getValue(resultPtr + 12, 'i32');
69-
if (statusPtr !== 1) {
70-
const errorMessage = wasmModule.UTF8ToString(errorMessagePtr);
71-
console.error(
72-
'Extraction failed with status:',
73-
statusPtr,
74-
'Error:',
75-
errorMessage
13+
export const initUntarJS = async (): Promise<IUnpackJSAPI> => {
14+
const wasmModule = await initializeWasm();
15+
16+
const extractData = async (data: Uint8Array): Promise<IFileData[]> => {
17+
/**Since WebAssembly, memory is accessed using pointers
18+
and the first parameter of extract_archive method from unpack.c, which is Uint8Array of file data, should be a pointer
19+
so we have to allocate memory for file data
20+
**/
21+
const inputPtr = wasmModule._malloc(data.length);
22+
wasmModule.HEAPU8.set(data, inputPtr);
23+
24+
// fileCountPtr is the pointer to 4 bytes of memory in WebAssembly's heap that holds fileCount value from the ExtractedArchive structure in unpack.c.
25+
const fileCountPtr = wasmModule._malloc(4);
26+
27+
try {
28+
const resultPtr = wasmModule._extract_archive(
29+
inputPtr,
30+
data.length,
31+
fileCountPtr
7632
);
33+
34+
/**
35+
* Since extract_archive returns a pointer that refers to an instance of the ExtractedArchive in unpack.c
36+
typedef struct {
37+
FileData* files;
38+
size_t fileCount;
39+
int status;
40+
char* error_message;
41+
} ExtractedArchive;
42+
43+
its fields are laid out in memory sequentially. Based on this and types each field will take 4 bytes:
44+
45+
4 bytes 4 bytes 4 bytes 4 bytes
46+
---------------|---------------|---------------|---------------
47+
files fileCount status error_message
48+
49+
`resultPtr` points to the beginning of the ExtractedArchive structure in WebAssembly memory
50+
and in order to get pointer of statusPtr we need to calculate it as: 0(offset of file pointer) + 4 (offset of fileCount) + 4 (offset for status)
51+
'status' field and pointer of `error_message` are 32-bit signed integer
52+
*/
53+
const statusPtr = wasmModule.getValue(resultPtr + 8, 'i32');
54+
const errorMessagePtr = wasmModule.getValue(resultPtr + 12, 'i32');
55+
if (statusPtr !== 1) {
56+
const errorMessage = wasmModule.UTF8ToString(errorMessagePtr);
57+
console.error(
58+
'Extraction failed with status:',
59+
statusPtr,
60+
'Error:',
61+
errorMessage
62+
);
63+
return [];
64+
}
65+
const filesPtr = wasmModule.getValue(resultPtr, 'i32');
66+
const fileCount = wasmModule.getValue(resultPtr + 4, 'i32');
67+
68+
const files: IFileData[] = [];
69+
70+
/**
71+
* FilesPtr is a pointer that refers to an instance of the FileData in unpack.c
72+
typedef struct {
73+
char* filename;
74+
uint8_t* data;
75+
size_t data_size;
76+
} FileData;
77+
78+
and its fields are laid out in memory sequentially too so each field take 4 bytes:
79+
80+
4 bytes 4 bytes 4 bytes
81+
---------------|---------------|---------------
82+
filename data data_size
83+
84+
`filesPtr + i * 12` calculates the memory address of the i-th FileData element in the array
85+
where `12` is the size of each FileData structure in memory in bytes: 4 + 4 + 4
86+
*/
87+
88+
for (let i = 0; i < fileCount; i++) {
89+
const fileDataPtr = filesPtr + i * 12;
90+
const filenamePtr = wasmModule.getValue(fileDataPtr, 'i32');
91+
const dataSize = wasmModule.getValue(fileDataPtr + 8, 'i32');
92+
const dataPtr = wasmModule.getValue(fileDataPtr + 4, 'i32');
93+
const filename = wasmModule.UTF8ToString(filenamePtr);
94+
const fileData = new Uint8Array(
95+
wasmModule.HEAPU8.buffer,
96+
dataPtr,
97+
dataSize
98+
);
99+
100+
files.push({
101+
filename: filename,
102+
data: fileData
103+
});
104+
}
105+
106+
wasmModule._free(inputPtr);
107+
wasmModule._free(fileCountPtr);
108+
wasmModule._free(errorMessagePtr);
109+
wasmModule._free(resultPtr);
110+
111+
return files;
112+
} catch (error) {
113+
console.error('Error during extraction:', error);
77114
return [];
78115
}
79-
const filesPtr = wasmModule.getValue(resultPtr, 'i32');
80-
const fileCount = wasmModule.getValue(resultPtr + 4, 'i32');
81-
82-
const files: IFileData[] = [];
83-
84-
/**
85-
* FilesPtr is a pointer that refers to an instance of the FileData in unpack.c
86-
typedef struct {
87-
char* filename;
88-
uint8_t* data;
89-
size_t data_size;
90-
} FileData;
91-
92-
and its fields are laid out in memory sequentially too so each field take 4 bytes:
93-
94-
4 bytes 4 bytes 4 bytes
95-
---------------|---------------|---------------
96-
filename data data_size
97-
98-
`filesPtr + i * 12` calculates the memory address of the i-th FileData element in the array
99-
where `12` is the size of each FileData structure in memory in bytes: 4 + 4 + 4
100-
*/
101-
102-
for (let i = 0; i < fileCount; i++) {
103-
const fileDataPtr = filesPtr + i * 12;
104-
const filenamePtr = wasmModule.getValue(fileDataPtr, 'i32');
105-
const dataSize = wasmModule.getValue(fileDataPtr + 8, 'i32');
106-
const dataPtr = wasmModule.getValue(fileDataPtr + 4, 'i32');
107-
const filename = wasmModule.UTF8ToString(filenamePtr);
108-
const fileData = new Uint8Array(
109-
wasmModule.HEAPU8.buffer,
110-
dataPtr,
111-
dataSize
112-
);
113-
114-
files.push({
115-
filename: filename,
116-
data: fileData
117-
});
116+
};
117+
118+
const extract = async (url: string): Promise<IFileData[]> => {
119+
try {
120+
const data = await fetchByteArray(url);
121+
return await extractData(data);
122+
} catch (error) {
123+
console.error('Error during extracting:', error);
124+
return [];
118125
}
119-
120-
wasmModule._free(inputPtr);
121-
wasmModule._free(fileCountPtr);
122-
wasmModule._free(errorMessagePtr);
123-
wasmModule._free(resultPtr);
124-
125-
return files;
126-
} catch (error) {
127-
console.error('Error during extraction:', error);
128-
return [];
129126
}
130-
};
131127

132-
export const extract = async (url: string): Promise<IFileData[]> => {
133-
try {
134-
const data = await fetchByteArray(url);
135-
return await extractData(data);
136-
} catch (error) {
137-
console.error('Error during extracting:', error);
138-
return [];
139-
}
128+
return {
129+
extract,
130+
extractData
131+
};
140132
};
141133

142-
143134
export * from './types';
144-
145-
export default {
146-
extract,
147-
extractData
148-
};

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ export interface IFileData {
22
filename: string;
33
data: Uint8Array;
44
}
5+
6+
export interface IUnpackJSAPI {
7+
extractData: (data: Uint8Array) => Promise<IFileData[]>;
8+
extract: (url: string) => Promise<IFileData[]>;
9+
}

0 commit comments

Comments
 (0)