├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .husky
└── pre-commit
├── LICENSE
├── README.md
├── benchmarks
├── public
│ ├── dist-web
│ │ ├── index.js
│ │ ├── index.js.br
│ │ ├── index.js.gz
│ │ └── index.mjs
│ ├── dist
│ │ ├── compiler.js
│ │ ├── core.js
│ │ └── index.js
│ ├── index.html
│ ├── index.mjs
│ ├── lib.mjs
│ └── tests
│ │ ├── firefox-multithreading.js
│ │ ├── iteration.js
│ │ ├── iterv2.js
│ │ ├── module-multithreading.js
│ │ ├── node-multithreading.js
│ │ ├── push.js
│ │ ├── workers-firefox
│ │ ├── arrayIterModule.js
│ │ └── vecIterModule.js
│ │ ├── workers-module
│ │ ├── arrayIterModule.js
│ │ └── vecIterModule.js
│ │ └── workers-node
│ │ ├── arrayIterModule.mjs
│ │ └── vecIterModule.mjs
└── server.mjs
├── buildInfo
└── index.js.br
├── cleanSingleLineComments.mjs
├── codegenTests
├── codegen.mjs
├── codegen.test.ts
├── default-js.mjs
├── default.ts
├── jest.config.mjs
├── named-js.mjs
└── named.ts
├── dist-deno
├── compiler.ts
├── core.ts
└── index.ts
├── dist-web
└── index.js
├── dist
├── compiler.d.ts
├── compiler.d.ts.map
├── compiler.js
├── core.d.ts
├── core.d.ts.map
├── core.js
├── index.d.ts
├── index.d.ts.map
└── index.js
├── jest.config.mjs
├── package.json
├── scripts
├── denoDistDir.mjs
├── distCopy.mjs
├── docgen.mjs
├── tsconfig.benchmarks.json
├── tsconfig.web.json
└── webBuild.mjs
├── src
├── compiler.ts
├── core.ts
├── index.ts
└── tests
│ ├── casting.test.ts
│ ├── compiler.test.ts
│ ├── generation.test.ts
│ ├── indexing.test.ts
│ ├── iterators.test.ts
│ ├── memory.test.ts
│ ├── mutations.test.ts
│ ├── naming.test.ts
│ └── references.test.ts
├── transformImports.mjs
└── tsconfig.json
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: "CI"
2 |
3 | on:
4 | push:
5 | branches: ["master", "dev"]
6 | pull_request:
7 | branches: ["master"]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: build_step
16 | run: npm i
17 | - name: run ci runner
18 | run: npm run ci
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | *.local.*
4 |
5 | package-lock.json
6 |
7 | **/__tmp__
8 | *.tmp.*
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run pre-commit
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Mostafa Elbannan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/benchmarks/public/dist-web/index.js.br:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moomoolive/struct-vec/5675e9cd4427856171c239d2419815d906f75df6/benchmarks/public/dist-web/index.js.br
--------------------------------------------------------------------------------
/benchmarks/public/dist-web/index.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moomoolive/struct-vec/5675e9cd4427856171c239d2419815d906f75df6/benchmarks/public/dist-web/index.js.gz
--------------------------------------------------------------------------------
/benchmarks/public/dist/compiler.js:
--------------------------------------------------------------------------------
1 | import { VALID_DATA_TYPES_INTERNAL } from "./core.js";
2 | const ALLOW_CHARACTERS_IN_VARIABLE_NAME = /^[A-Za-z_\\$][A-Za-z0-9_\\$]*$/;
3 | export const ERR_PREFIX = "[VecGenerator]";
4 | function validVariableName(candidate) {
5 | return ALLOW_CHARACTERS_IN_VARIABLE_NAME.test(candidate);
6 | }
7 | function validType(type) {
8 | switch (type) {
9 | case "f32":
10 | case "i32":
11 | case "bool":
12 | case "char":
13 | return true;
14 | default:
15 | return false;
16 | }
17 | }
18 | function restrictedFieldName(name) {
19 | switch (name) {
20 | case "self":
21 | case "e":
22 | case "_viewingIndex":
23 | case "ref":
24 | case "index":
25 | return true;
26 | default:
27 | return false;
28 | }
29 | }
30 | const BITS_IN_I32 = 32;
31 | export function tokenizeStructDef(def) {
32 | if (typeof def !== "object" || def === null || Array.isArray(def)) {
33 | throw SyntaxError(`${ERR_PREFIX} inputted invalid struct def. Expected object in the form of '{"field1": "f32", "field2": "char", "field3": "bool"}' got ${JSON.stringify(def)}`);
34 | }
35 | const fieldDefs = Object.keys(def).map((key) => {
36 | return { field: key, type: def[key] };
37 | });
38 | if (fieldDefs.length < 1) {
39 | throw SyntaxError(`${ERR_PREFIX} struct definition must have at least one key`);
40 | }
41 | let elementSize = 0;
42 | const tokens = {
43 | elementSize: 0,
44 | fieldNames: [],
45 | float32Fields: [],
46 | int32Fields: [],
47 | booleanFields: [],
48 | charFields: []
49 | };
50 | const float32Types = [];
51 | const int32Types = [];
52 | const boolTypes = [];
53 | const charTypes = [];
54 | for (let i = 0; i < fieldDefs.length; i += 1) {
55 | const { field, type } = fieldDefs[i];
56 | if (typeof field !== "string" || !validVariableName(field)) {
57 | throw SyntaxError(`${ERR_PREFIX} Bracket notation is disallowed, all structDef must be indexable by dot notation. Field "${field}" of struct requires indexing as "vec['${field}']" which is disallowed. Consider removing any hyphens.`);
58 | }
59 | else if (restrictedFieldName(field)) {
60 | throw SyntaxError(`${ERR_PREFIX} field "${field}" is a reserved name.`);
61 | }
62 | if (typeof type !== "string") {
63 | throw SyntaxError(`${ERR_PREFIX} field "${field}" is not a string, got "${typeof type}". Struct definition field values must be a string of ${VALID_DATA_TYPES_INTERNAL.join(", ")}`);
64 | }
65 | else if (!validType(type)) {
66 | throw SyntaxError(`${ERR_PREFIX} field "${field}" is not a valid type (got type "${type}"). Struct definition fields can only be of type of ${VALID_DATA_TYPES_INTERNAL.join(", ")}`);
67 | }
68 | switch (type) {
69 | case "f32":
70 | float32Types.push(field);
71 | break;
72 | case "i32":
73 | int32Types.push(field);
74 | break;
75 | case "bool":
76 | boolTypes.push(field);
77 | break;
78 | case "char":
79 | charTypes.push(field);
80 | break;
81 | }
82 | }
83 | float32Types.sort();
84 | for (let i = 0; i < float32Types.length; i += 1) {
85 | const field = float32Types[i];
86 | tokens.fieldNames.push(field);
87 | tokens.float32Fields.push({
88 | field,
89 | offset: elementSize
90 | });
91 | elementSize += 1;
92 | }
93 | int32Types.sort();
94 | for (let i = 0; i < int32Types.length; i += 1) {
95 | const field = int32Types[i];
96 | tokens.fieldNames.push(field);
97 | tokens.int32Fields.push({
98 | field,
99 | offset: elementSize
100 | });
101 | elementSize += 1;
102 | }
103 | charTypes.sort();
104 | for (let i = 0; i < charTypes.length; i += 1) {
105 | const field = charTypes[i];
106 | tokens.fieldNames.push(field);
107 | tokens.charFields.push({
108 | field,
109 | offset: elementSize
110 | });
111 | elementSize += 1;
112 | }
113 | boolTypes.sort();
114 | let start = 0;
115 | while (start < boolTypes.length) {
116 | const boolsLeft = boolTypes.length - start;
117 | const end = boolsLeft < BITS_IN_I32
118 | ? boolsLeft
119 | : BITS_IN_I32;
120 | for (let i = start; i < start + end; i += 1) {
121 | const field = boolTypes[i];
122 | tokens.fieldNames.push(field);
123 | tokens.booleanFields.push({
124 | field,
125 | offset: elementSize,
126 | byteOffset: i - start
127 | });
128 | }
129 | elementSize += 1;
130 | start += BITS_IN_I32;
131 | }
132 | tokens.elementSize = elementSize;
133 | return tokens;
134 | }
135 | function reservedJsKeyword(word) {
136 | switch (word) {
137 | case "false":
138 | case "true":
139 | case "null":
140 | case "await":
141 | case "static":
142 | case "public":
143 | case "protected":
144 | case "private":
145 | case "package":
146 | case "let":
147 | case "interface":
148 | case "implements":
149 | case "yield":
150 | case "with":
151 | case "while":
152 | case "void":
153 | case "var":
154 | case "typeof":
155 | case "try":
156 | case "throw":
157 | case "this":
158 | case "switch":
159 | case "super":
160 | case "return":
161 | case "new":
162 | case "instanceof":
163 | case "in":
164 | case "import":
165 | case "if":
166 | case "function":
167 | case "for":
168 | case "finally":
169 | case "extends":
170 | case "export":
171 | case "else":
172 | case "do":
173 | case "delete":
174 | case "default":
175 | case "debugger":
176 | case "continue":
177 | case "const":
178 | case "class":
179 | case "catch":
180 | case "case":
181 | case "break":
182 | return true;
183 | default:
184 | return false;
185 | }
186 | }
187 | export function invalidClassName(name) {
188 | return (!validVariableName(name)
189 | || reservedJsKeyword(name)
190 | || name.length < 1);
191 | }
192 | export function validateCompileOptions(input) {
193 | if (typeof input !== "object"
194 | || input === null
195 | || Array.isArray(input)) {
196 | throw TypeError(`input options must be of type "object", got type "${typeof input}"`);
197 | }
198 | if (typeof input.pathToLib !== "string"
199 | || !input.pathToLib) {
200 | throw TypeError("option 'pathToLib' missing");
201 | }
202 | if (typeof input.className !== "string"
203 | || invalidClassName(input.className)) {
204 | throw SyntaxError(`inputted class name is not a valid javascript class name, got "${input.className}"`);
205 | }
206 | switch (input.exportSyntax) {
207 | case "named":
208 | case "default":
209 | case "none":
210 | break;
211 | default:
212 | throw TypeError("invalid export Syntax option. exportSyntax must be either 'none', 'named', or 'default', got '" + input.exportSyntax + "''");
213 | }
214 | if (input.lang !== "js" && input.lang !== "ts") {
215 | throw TypeError(`option "bindings" must be either "js" or "ts". Got "${input.bindings}"`);
216 | }
217 | }
218 | export function createVecDef(tokens, structDef, { lang, pathToLib, className, exportSyntax, runtimeCompile }) {
219 | const { elementSize, fieldNames, float32Fields, booleanFields, charFields, int32Fields } = tokens;
220 | const def = JSON.stringify(structDef);
221 | const ts = lang === "ts";
222 | const generic = `<${def}>`;
223 | const libPath = `"${pathToLib}"`;
224 | const importStatement = `import {Vec${ts ? ", StructDef, Struct, CursorConstructor, VecCursor, DetachedVecCursor" : ""}} from ${libPath}`;
225 | const CursorConstructor = "CursorConstructor" + generic;
226 | const memory = ts ?
227 | "(this.self as unknown as {_f32Memory: Float32Array})._f32Memory"
228 | : "this.self._f32Memory";
229 | const intMemory = ts ?
230 | "(this.self as unknown as {_i32Memory: Int32Array})._i32Memory"
231 | : "this.self._i32Memory";
232 | return {
233 | className,
234 | def: `
235 | ${pathToLib !== "none" ? importStatement : ""}
236 | ${ts || runtimeCompile
237 | ? ""
238 | : `/**
239 | * @extends {Vec${generic}}
240 | */`}
241 | ${exportSyntax === "named" ? "export " : ""}class ${className} extends Vec${ts ? generic : ""} {
242 | static ${ts ? "readonly " : ""}def${ts ? ": StructDef" : ""} = ${def}
243 | static ${ts ? "readonly " : ""}elementSize${ts ? ": number" : ""} = ${elementSize}
244 | ${ts ? "protected " : ""}static Cursor = class ${className}Cursor {
245 | ${ts ? `_viewingIndex: number\n\t\tself: Vec${generic}` : ""}
246 | constructor(self${ts ? ": Vec" + generic : ""}, index${ts ? ": number" : ""}) { this.self = self;this._viewingIndex = index}
247 | ${float32Fields.map(({ field, offset }) => {
248 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
249 | const base = `${memory}[this._viewingIndex${fieldOffset}]`;
250 | const type = ts ? ": number" : "";
251 | const getter = `get ${field}()${type} { return ${base} }`;
252 | const setter = `set ${field}(newValue${type}) { ${base} = newValue }`;
253 | return `${getter}; ${setter};`;
254 | }).join("\n\t ")}
255 | ${int32Fields.map(({ field, offset }) => {
256 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
257 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`;
258 | const type = ts ? ": number" : "";
259 | const getter = `get ${field}()${type} { return ${base} }`;
260 | const setter = `set ${field}(newValue${type}) { ${base} = newValue }`;
261 | return `${getter}; ${setter};`;
262 | }).join("\n\t ")}
263 | ${charFields.map(({ field, offset }) => {
264 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
265 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`;
266 | const type = ts ? ": string" : "";
267 | const getter = `get ${field}()${type} { return String.fromCodePoint(${base} || ${32}) }`;
268 | const setter = `set ${field}(newValue${type}) { ${base} = newValue.codePointAt(0) || ${32} }`;
269 | return `${getter}; ${setter};`;
270 | }).join("\n\t ")}
271 | ${booleanFields.map(({ field, offset, byteOffset }) => {
272 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
273 | const mask = 1 << byteOffset;
274 | const reverseMask = ~mask;
275 | const type = ts ? ": boolean" : "";
276 | const boolCast = ts ? "(Boolean(newValue) as unknown as number)" : "Boolean(newValue)";
277 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`;
278 | const getter = `get ${field}()${type} { return Boolean(${base} & ${mask}) }`;
279 | const setter = `set ${field}(newValue${type}) { ${base} &= ${reverseMask};${base} |= ${boolCast}${byteOffset < 1 ? "" : " << " + byteOffset.toString()}}`;
280 | return `${getter}; ${setter};`;
281 | }).join("\n\t ")}
282 | set e({${fieldNames.map((field) => field).join(", ")}}${ts ? ": Struct" + generic : ""}) { ${fieldNames.map((field) => {
283 | return "this." + field + " = " + field;
284 | }).join(";")}; }
285 | get e()${ts ? ": Struct" + generic : ""} { return {${fieldNames.map((field) => {
286 | return field + ": this." + field;
287 | }).join(", ")}} }
288 | get ref()${ts ? `: VecCursor${generic}` : ""} { return new ${className}.Cursor(this.self, this._viewingIndex) }
289 | index(index${ts ? ": number" : ""})${ts ? `: DetachedVecCursor${generic}` : ""} { this._viewingIndex = index * this.self.elementSize; return this }
290 | }${ts ? " as " + CursorConstructor : ""}
291 | get elementSize()${ts ? ": number" : ""} { return ${elementSize} }
292 | get def()${ts ? ": StructDef" : ""} { return ${def} }
293 | ${ts ? "protected " : ""}get cursorDef()${ts ? ": " + CursorConstructor : ""} { return ${className}.Cursor }
294 | }
295 |
296 | ${exportSyntax === "default" ? `export default {${className}}` : ""}
297 | `.trim()
298 | };
299 | }
300 |
--------------------------------------------------------------------------------
/benchmarks/public/dist/index.js:
--------------------------------------------------------------------------------
1 | import { Vec as BaseVec } from "./core.js";
2 | import { tokenizeStructDef, ERR_PREFIX, createVecDef, validateCompileOptions, invalidClassName } from "./compiler.js";
3 | export { Vec } from "./core.js";
4 | export function validateStructDef(def) {
5 | try {
6 | tokenizeStructDef(def);
7 | return true;
8 | }
9 | catch (_a) {
10 | return false;
11 | }
12 | }
13 | export function vec(structDef, options = {}) {
14 | if (typeof SharedArrayBuffer === "undefined") {
15 | throw new Error(`${ERR_PREFIX} sharedArrayBuffers are not supported in this environment and are required for vecs`);
16 | }
17 | const tokens = tokenizeStructDef(structDef);
18 | const { className = "AnonymousVec" } = options;
19 | if (invalidClassName(className)) {
20 | throw SyntaxError(`inputted class name (className option) is not a valid javascript class name, got "${className}"`);
21 | }
22 | const { def, className: clsName } = createVecDef(tokens, structDef, {
23 | lang: "js",
24 | exportSyntax: "none",
25 | pathToLib: "none",
26 | className,
27 | runtimeCompile: true
28 | });
29 | const genericVec = Function(`"use strict";return (Vec) => {
30 | ${def}
31 | return ${clsName}
32 | }`)()(BaseVec);
33 | return genericVec;
34 | }
35 | export function vecCompile(structDef, pathToLib, options = {}) {
36 | const { lang = "js", exportSyntax = "none", className = "AnonymousVec" } = options;
37 | const compilerArgs = {
38 | lang,
39 | pathToLib,
40 | className,
41 | exportSyntax,
42 | runtimeCompile: false
43 | };
44 | validateCompileOptions(compilerArgs);
45 | const tokens = tokenizeStructDef(structDef);
46 | const { def } = createVecDef(tokens, structDef, compilerArgs);
47 | return def;
48 | }
49 | export default {
50 | vec,
51 | Vec: BaseVec,
52 | validateStructDef,
53 | vecCompile
54 | };
55 |
--------------------------------------------------------------------------------
/benchmarks/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/benchmarks/public/index.mjs:
--------------------------------------------------------------------------------
1 | import {vec} from "../../dist/index.js"
2 | import {Benchmark} from "./lib.mjs"
3 |
4 | const Position = vec({x: "f32", y: "f32", z: "f32"})
5 |
6 | const elementCount = 10_000_000
7 |
8 | const benchmark = new Benchmark()
9 | benchmark
10 | .add("vec push", () => {
11 | const container = new Position()
12 | for (let i = 0; i < elementCount; i += 1) {
13 | container.push({x: 1, y: 1, z: 1})
14 | }
15 | })
16 | .add("arr push", () => {
17 | const container = []
18 | for (let i = 0; i < elementCount; i += 1) {
19 | container.push({x: 1, y: 1, z: 1})
20 | }
21 | })
22 | /*
23 | .add("vec push with pre-alloc", () => {
24 | const container = new Position()
25 | container.reserve(elementCount)
26 | for (let i = 0; i < elementCount; i += 1) {
27 | container.push({x: 1, y: 1, z: 1})
28 | }
29 | })
30 | .add("arr push with pre-alloc", () => {
31 | const container = new Array(elementCount)
32 | for (let i = 0; i < elementCount; i += 1) {
33 | container.push({x: 1, y: 1, z: 1})
34 | }
35 | })
36 | */
37 | .run()
38 |
--------------------------------------------------------------------------------
/benchmarks/public/lib.mjs:
--------------------------------------------------------------------------------
1 | export class Benchmark {
2 | WARM_UP_RUNS = 4
3 | numberOfRuns = 100 + this.WARM_UP_RUNS
4 | names = {}
5 | tests = []
6 |
7 | add(name, callback) {
8 | this.tests.push({name, callback})
9 | return this
10 | }
11 |
12 | setNumberOfIterations(number) {
13 | if (number < 0) {
14 | throw new Error(`number of iterations cannot be < 0`)
15 | }
16 | this.numberOfRuns = number + this.WARM_UP_RUNS
17 | return this
18 | }
19 |
20 | async run() {
21 | const results = {}
22 |
23 | this.tests.forEach(({name}) => {
24 | results[name] = {deltas: [], avg: 0}
25 | })
26 |
27 | console.info("starting warmup, please wait a moment...")
28 |
29 | for (let iter = 0; iter < this.numberOfRuns; iter += 1) {
30 | if (iter === this.WARM_UP_RUNS) {
31 | console.info("warmup finished. commencing tests\n")
32 | }
33 | for (let i = 0; i < this.tests.length; i++) {
34 | const {name, callback} = this.tests[i]
35 | const t1 = Date.now()
36 | await callback()
37 | const t2 = Date.now()
38 | const delta = t2 - t1
39 | results[name].deltas.push(delta)
40 | if (iter >= this.WARM_UP_RUNS) {
41 | console.info(`[iteration ${(iter + 1) - this.WARM_UP_RUNS}]:"${name}" took ${delta} ms`)
42 | }
43 | }
44 | if (iter >= this.WARM_UP_RUNS) { console.log("\n") }
45 | }
46 |
47 | console.log("\n")
48 |
49 | Object.keys(results).forEach((testName) => {
50 | const value = results[testName]
51 | const sum = value.deltas.slice(this.WARM_UP_RUNS).reduce((total, res) => {
52 | return total + res
53 | })
54 | const totalRuns = this.numberOfRuns - this.WARM_UP_RUNS
55 | value.avg = sum / totalRuns
56 | value.stdDeviation = value.deltas
57 | .slice(this.WARM_UP_RUNS)
58 | .map((delta) => Math.pow(delta - value.avg, 2))
59 | .reduce((total, squaredDiff) => total + squaredDiff, 0)
60 | value.stdDeviation = Math.sqrt(value.stdDeviation / totalRuns)
61 | console.info(`"${testName}" took an average of ${value.avg.toFixed(2)} ms ±${value.stdDeviation.toFixed(2)} (${this.numberOfRuns - this.WARM_UP_RUNS} runs)`)
62 | })
63 |
64 | return results
65 | }
66 | }
--------------------------------------------------------------------------------
/benchmarks/public/tests/firefox-multithreading.js:
--------------------------------------------------------------------------------
1 | import {vec} from "./dist/index.js"
2 | import {Benchmark} from "./lib.mjs"
3 |
4 | const Position = vec({x: "num", y: "num", z: "num", w: "num"})
5 |
6 | const elementCount = 8_000_000
7 |
8 | const numberOfCores = 3
9 | const vecWorkers = []
10 | for (let i = 0; i < numberOfCores; i++) {
11 | vecWorkers.push(new Worker(
12 | new URL(
13 | "./tests/workers-firefox/vecIterModule.js",
14 | import.meta.url
15 | ).href,
16 | ))
17 | }
18 |
19 |
20 | const arrWorkers = []
21 | for (let i = 0; i < numberOfCores; i++) {
22 | arrWorkers.push(new Worker(
23 | new URL("./tests/workers-firefox/arrayIterModule.js", import.meta.url).href,
24 | ))
25 | }
26 |
27 | const positionVec = new Position(elementCount)
28 | const positionArr = []
29 |
30 | for (let i = 0; i < elementCount; i += 1) {
31 | positionArr.push({x: 2, y: 2, z: 2, w: 2})
32 | }
33 | for (let i = 0; i < elementCount; i += 1) {
34 | positionVec.push({x: 2, y: 2, z: 2, w: 2})
35 | }
36 |
37 | function factorial(n) {
38 | if (n < 2) {
39 | return 1
40 | } else {
41 | return n * factorial(n - 1)
42 | }
43 | }
44 |
45 | async function main() {
46 | const benchmark = new Benchmark()
47 | await benchmark
48 | .setNumberOfIterations(10)
49 | .add("vec single thread", () => {
50 | const len = positionVec.length
51 | for (let i = 0; i < len; i += 1) {
52 | positionVec.index(i).y += factorial(
53 | Math.floor(Math.random() * 10) + 95
54 | )
55 | }
56 | })
57 | .add("vec multi-core", async () => {
58 | const len = positionVec.length
59 | const numberOfElementsPerWorker = len / (numberOfCores + 1)
60 | const ps = []
61 | for (let i = 0; i < vecWorkers.length; i++) {
62 | const worker = vecWorkers[i]
63 | ps.push(new Promise(resolve => {
64 | worker.onmessage = () => resolve()
65 | worker.postMessage({
66 | memory: positionVec.memory,
67 | start: i * numberOfElementsPerWorker,
68 | end: (i * numberOfElementsPerWorker) + numberOfElementsPerWorker
69 | })
70 | }))
71 | }
72 | for (let i = 4 * numberOfElementsPerWorker; i < len; i += 1) {
73 | positionVec.index(i).y += factorial(
74 | Math.floor(Math.random() * 10) + 95
75 | )
76 | }
77 | await Promise.all(ps)
78 | })
79 | /*
80 | .add("array single thread", () => {
81 | for (let i = 0; i < positionArr.length; i += 1) {
82 | positionArr[i].y = factorial(
83 | Math.floor(Math.random() * 10) + 95
84 | )
85 | }
86 | })
87 | */
88 | /*
89 | .add("array multi-threaded", async () => {
90 | const len = positionArr.length
91 | const numberOfElementsPerWorker = len / (numberOfCores + 1)
92 | const ps = []
93 | for (let i = 0; i < arrWorkers.length; i++) {
94 | const worker = arrWorkers[i]
95 | ps.push(new Promise(resolve => {
96 | worker.onmessage = () => resolve()
97 | worker.postMessage({
98 | arr: positionArr,
99 | start: i * numberOfElementsPerWorker,
100 | end: (i * numberOfElementsPerWorker) + numberOfElementsPerWorker
101 | })
102 | }))
103 | }
104 | for (let i = 4 * numberOfElementsPerWorker; i < len; i += 1) {
105 | positionArr[i].y += factorial(
106 | Math.floor(Math.random() * 10) + 95
107 | )
108 | }
109 | await Promise.all(ps)
110 | })
111 | */
112 | .run()
113 | }
114 |
115 | main()
116 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/iteration.js:
--------------------------------------------------------------------------------
1 | import {vec} from "../../dist/index.js"
2 | import {Benchmark} from "./lib.mjs"
3 |
4 | const Position = vec({
5 | x: "i32",
6 | y: "i32",
7 | z: "i32"
8 | })
9 |
10 | const elementCount = 10_000_000
11 |
12 | const positionVec = new Position(1_000_000)
13 | const positionArr = []
14 | const rawTypedArray = new Float64Array(elementCount * 3)
15 |
16 | for (let i = 0; i < elementCount; i += 1) {
17 | positionArr.push({x: 1, y: 1, z: 1})
18 | }
19 | for (let i = 0; i < elementCount; i += 1) {
20 | positionVec.push({x: 1, y: 1, z: 1})
21 | }
22 |
23 | const tArrayLength = (elementCount * 3)
24 |
25 | const benchmark = new Benchmark()
26 | benchmark
27 | .add("typed array imperative loop", () => {
28 | for (let i = 0; i < tArrayLength; i += 3) {
29 | rawTypedArray[i + 1] += 10
30 | }
31 | })
32 | /*
33 | .add("array iterator", () => {
34 | positionArr.forEach(e => e.x += 10)
35 | })
36 | */
37 | .add("array imperative loop", () => {
38 | for (let i = 0; i < positionArr.length; i += 1) {
39 | positionArr[i].y += 10
40 | }
41 | })
42 | /*
43 | .add("array es6 iterator", () => {
44 | for (const position of positionArr) {
45 | position.x += 10
46 | }
47 | })
48 | */
49 | .add("vec imperative loop", () => {
50 | for (let i = 0; i < elementCount; i += 1) {
51 | positionVec.index(i).y += 10
52 | }
53 | })
54 | /*
55 | .add("vec iterator", () => {
56 | positionVec.forEach((e) => e.x += 10)
57 | })
58 | .add("vec es6 iterator", () => {
59 | for (const position of positionVec) {
60 | position.x += 10
61 | }
62 | })
63 | */
64 | .run()
65 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/iterv2.js:
--------------------------------------------------------------------------------
1 | import {vec} from "./dist/index.js"
2 | import {Benchmark} from "./lib.js"
3 |
4 | const Position = vec({x: "num", y: "num", z: "num", w: "num"})
5 |
6 | const _anotherVec = vec({zz: "num"})
7 |
8 | const elementCount = 10_000_000
9 |
10 | const positionVec = new Position(elementCount)
11 | const positionArr = []
12 | const rawTypedArray = new Float64Array(elementCount * 4)
13 |
14 | for (let i = 0; i < elementCount; i += 1) {
15 | positionArr.push({x: 2, y: 2, z: 2, w: 2})
16 | }
17 | for (let i = 0; i < elementCount; i += 1) {
18 | positionVec.push({x: 2, y: 2, z: 2, w: 2})
19 | }
20 |
21 | const benchmark = new Benchmark()
22 | benchmark
23 | .add("typed array imperative loop", () => {
24 | for (let i = 0; i < rawTypedArray.length; i += 4) {
25 | rawTypedArray[i + 1] += 10
26 | }
27 | })
28 | /*
29 | .add("array iterator", () => {
30 | positionArr.forEach(e => e.y += 10)
31 | })
32 | */
33 | /*
34 | .add("array imperative loop", () => {
35 | for (let i = 0; i < positionArr.length; i += 1) {
36 | positionArr[i].y += 10
37 | }
38 | })
39 | */
40 | .add("array es6 iterator", () => {
41 | for (const position of positionArr) {
42 | position.y += 10
43 | }
44 | })
45 | /*
46 | .add("vec imperative loop", () => {
47 | for (let i = 0; i < elementCount; i += 1) {
48 | positionVec.index(i).y += 10
49 | }
50 | })
51 | */
52 | /*
53 | .add("vec iterator", () => {
54 | positionVec.forEach((e) => e.y += 10)
55 | })
56 | */
57 | .add("vec es6 iterator", () => {
58 | for (const position of positionVec) {
59 | position.y += 10
60 | }
61 | })
62 | .run()
63 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/module-multithreading.js:
--------------------------------------------------------------------------------
1 | import {vec} from "./dist/index.js"
2 | import {Benchmark} from "./lib.mjs"
3 |
4 | const Position = vec({x: "num", y: "num", z: "num", w: "num"})
5 |
6 | const elementCount = 5_000_000
7 |
8 | const numberOfCores = 4
9 | const vecWorkers = []
10 | for (let i = 0; i < numberOfCores; i++) {
11 | vecWorkers.push(new Worker(
12 | new URL("./tests/workers-module/vecIterModule.js", import.meta.url).href,
13 | {type: "module"}
14 | ))
15 | }
16 |
17 | const arrWorkers = []
18 | for (let i = 0; i < numberOfCores; i++) {
19 | arrWorkers.push(new Worker(
20 | new URL("./tests/workers-module/arrayIterModule.js", import.meta.url).href,
21 | {type: "module"}
22 | ))
23 | }
24 |
25 | const positionVec = new Position(elementCount)
26 | const positionArr = []
27 |
28 | for (let i = 0; i < elementCount; i += 1) {
29 | positionArr.push({x: 2, y: 2, z: 2, w: 2})
30 | }
31 | for (let i = 0; i < elementCount; i += 1) {
32 | positionVec.push({x: 2, y: 2, z: 2, w: 2})
33 | }
34 |
35 | function factorial(n) {
36 | if (n < 2) {
37 | return 1
38 | } else {
39 | return n * factorial(n - 1)
40 | }
41 | }
42 |
43 | async function main() {
44 | const benchmark = new Benchmark()
45 | await benchmark
46 | .setNumberOfIterations(10)
47 | /*
48 | .add("array single thread", () => {
49 | for (let i = 0; i < positionArr.length; i += 1) {
50 | positionArr[i].y = factorial(
51 | Math.floor(Math.random() * 10) + 95
52 | )
53 | }
54 | })
55 |
56 | .add("array multi-threaded", async () => {
57 | const len = positionArr.length
58 | const numberOfElementsPerWorker = len / (numberOfCores + 1)
59 | const ps = []
60 | for (let i = 0; i < arrWorkers.length; i++) {
61 | const worker = arrWorkers[i]
62 | ps.push(new Promise(resolve => {
63 | worker.onmessage = () => resolve()
64 | worker.postMessage({
65 | arr: positionArr,
66 | start: i * numberOfElementsPerWorker,
67 | end: (i * numberOfElementsPerWorker) + numberOfElementsPerWorker
68 | })
69 | }))
70 | }
71 | for (let i = 4 * numberOfElementsPerWorker; i < len; i += 1) {
72 | positionArr[i].y += factorial(
73 | Math.floor(Math.random() * 10) + 95
74 | )
75 | }
76 | await Promise.all(ps)
77 | })
78 | */
79 | .add("vec single thread", () => {
80 | const len = positionVec.length
81 | for (let i = 0; i < len; i += 1) {
82 | positionVec.index(i).y += factorial(
83 | Math.floor(Math.random() * 10) + 95
84 | )
85 | }
86 | })
87 | .add("vec multi-core", async () => {
88 | const len = positionVec.length
89 | const numberOfElementsPerWorker = len / (numberOfCores + 1)
90 | const ps = []
91 | for (let i = 0; i < vecWorkers.length; i++) {
92 | const worker = vecWorkers[i]
93 | ps.push(new Promise(resolve => {
94 | worker.onmessage = () => resolve()
95 | worker.postMessage({
96 | memory: positionVec.memory,
97 | start: i * numberOfElementsPerWorker,
98 | end: (i * numberOfElementsPerWorker) + numberOfElementsPerWorker
99 | })
100 | }))
101 | }
102 | for (let i = 4 * numberOfElementsPerWorker; i < len; i += 1) {
103 | positionVec.index(i).y += factorial(
104 | Math.floor(Math.random() * 10) + 95
105 | )
106 | }
107 | await Promise.all(ps)
108 | })
109 | .run()
110 | }
111 |
112 | main()
113 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/node-multithreading.js:
--------------------------------------------------------------------------------
1 | import {Worker} from "worker_threads"
2 | import {vec} from "../../dist/index.js"
3 | import {Benchmark} from "./lib.mjs"
4 | import {dirname} from 'path'
5 | import {fileURLToPath} from 'url'
6 | import path from "path"
7 |
8 | const __filename = fileURLToPath(import.meta.url)
9 | const __dirname = dirname(__filename)
10 |
11 | const Position = vec({x: "num", y: "num", z: "num", w: "num"})
12 |
13 | const elementCount = 5_000_000
14 |
15 | const numberOfCores = 4
16 | const vecWorkers = []
17 | for (let i = 0; i < numberOfCores; i++) {
18 | vecWorkers.push(new Worker(
19 | path.join(__dirname, "/tests/workers-node/vecIterModule.mjs"),
20 | //{type: "module"}
21 | ))
22 | }
23 |
24 | const arrWorkers = []
25 | for (let i = 0; i < numberOfCores; i++) {
26 | arrWorkers.push(new Worker(
27 | path.join(__dirname, "/tests/workers-node/arrayIterModule.mjs"),
28 | //{type: "module"}
29 | ))
30 | }
31 |
32 | const positionVec = new Position(elementCount)
33 | const positionArr = []
34 |
35 | for (let i = 0; i < elementCount; i += 1) {
36 | positionArr.push({x: 2, y: 2, z: 2, w: 2})
37 | }
38 | for (let i = 0; i < elementCount; i += 1) {
39 | positionVec.push({x: 2, y: 2, z: 2, w: 2})
40 | }
41 |
42 | function factorial(n) {
43 | if (n < 2) {
44 | return 1
45 | } else {
46 | return n * factorial(n - 1)
47 | }
48 | }
49 |
50 | async function main() {
51 | const benchmark = new Benchmark()
52 | await benchmark
53 | .setNumberOfIterations(10)
54 | /*
55 | .add("array single thread", () => {
56 | for (let i = 0; i < positionArr.length; i += 1) {
57 | positionArr[i].y = factorial(
58 | Math.floor(Math.random() * 10) + 95
59 | )
60 | }
61 | })
62 |
63 | .add("array multi-threaded", async () => {
64 | const len = positionArr.length
65 | const numberOfElementsPerWorker = len / (numberOfCores + 1)
66 | const ps = []
67 | for (let i = 0; i < arrWorkers.length; i++) {
68 | const worker = arrWorkers[i]
69 | ps.push(new Promise(resolve => {
70 | worker.on("message", resolve)
71 | worker.postMessage({
72 | arr: positionArr,
73 | start: i * numberOfElementsPerWorker,
74 | end: (i * numberOfElementsPerWorker) + numberOfElementsPerWorker
75 | })
76 | }))
77 | }
78 | for (let i = 4 * numberOfElementsPerWorker; i < len; i += 1) {
79 | positionArr[i].y += factorial(
80 | Math.floor(Math.random() * 10) + 95
81 | )
82 | }
83 | await Promise.all(ps)
84 | })
85 | */
86 | .add("vec single thread", () => {
87 | const len = positionVec.length
88 | for (let i = 0; i < len; i += 1) {
89 | positionVec.index(i).y += factorial(
90 | Math.floor(Math.random() * 10) + 95
91 | )
92 | }
93 | })
94 | .add("vec multi-core", async () => {
95 | const len = positionVec.length
96 | const numberOfElementsPerWorker = len / (numberOfCores + 1)
97 | const ps = []
98 | for (let i = 0; i < vecWorkers.length; i++) {
99 | const worker = vecWorkers[i]
100 | ps.push(new Promise(resolve => {
101 | worker.on("message", resolve)
102 | worker.postMessage({
103 | memory: positionVec.memory,
104 | start: i * numberOfElementsPerWorker,
105 | end: (i * numberOfElementsPerWorker) + numberOfElementsPerWorker
106 | })
107 | }))
108 | }
109 | for (let i = 4 * numberOfElementsPerWorker; i < len; i += 1) {
110 | positionVec.index(i).y += factorial(
111 | Math.floor(Math.random() * 10) + 95
112 | )
113 | }
114 | await Promise.all(ps)
115 | })
116 | .run()
117 | }
118 |
119 | main()
120 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/push.js:
--------------------------------------------------------------------------------
1 | import {vec} from "./dist/index.js"
2 | import {Benchmark} from "./lib.js"
3 |
4 | const Position = vec({x: "num", y: "num", z: "num"})
5 |
6 | const elementCount = 10_000_000
7 |
8 | const benchmark = new Benchmark()
9 | benchmark
10 | .add("vec push", () => {
11 | const container = new Position()
12 | for (let i = 0; i < elementCount; i += 1) {
13 | container.push({x: 1, y: 1, z: 1})
14 | }
15 | })
16 | .add("arr push", () => {
17 | const container = []
18 | for (let i = 0; i < elementCount; i += 1) {
19 | container.push({x: 1, y: 1, z: 1})
20 | }
21 | })
22 | /*
23 | .add("vec push with pre-alloc", () => {
24 | const container = new Position()
25 | container.reserve(elementCount)
26 | for (let i = 0; i < elementCount; i += 1) {
27 | container.push({x: 1, y: 1, z: 1})
28 | }
29 | })
30 | .add("arr push with pre-alloc", () => {
31 | const container = new Array(elementCount)
32 | for (let i = 0; i < elementCount; i += 1) {
33 | container.push({x: 1, y: 1, z: 1})
34 | }
35 | })
36 | */
37 | .run()
38 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/workers-firefox/arrayIterModule.js:
--------------------------------------------------------------------------------
1 | console.log("array worker init")
2 |
3 | function factorial(n) {
4 | if (n < 2) {
5 | return 1
6 | } else {
7 | return n * factorial(n - 1)
8 | }
9 | }
10 |
11 | self.onmessage = ({data}) => {
12 | const {arr, start, end} = data
13 | for (let i = start; i < end; i++) {
14 | arr[i].y += factorial(
15 | Math.floor(Math.random() * 10) + 95
16 | )
17 | }
18 | self.postMessage(true)
19 | }
20 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/workers-firefox/vecIterModule.js:
--------------------------------------------------------------------------------
1 | self.importScripts("../../dist-web/index.js")
2 | const {vec} = structVec
3 |
4 | console.log("firefox worker init")
5 |
6 | const Position = vec({x: "num", y: "num", z: "num", w: "num"})
7 |
8 | function factorial(n) {
9 | if (n < 2) {
10 | return 1
11 | } else {
12 | return n * factorial(n - 1)
13 | }
14 | }
15 |
16 | const pos = new Position(1)
17 |
18 | self.onmessage = ({data}) => {
19 | const {memory, start, end} = data
20 | pos.memory = memory
21 | for (let i = start; i < end; i += 1) {
22 | pos.index(i).y += factorial(
23 | Math.floor(Math.random() * 10) + 95
24 | )
25 | }
26 | self.postMessage(true)
27 | }
28 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/workers-module/arrayIterModule.js:
--------------------------------------------------------------------------------
1 | console.log("array worker init")
2 |
3 | function factorial(n) {
4 | if (n < 2) {
5 | return 1
6 | } else {
7 | return n * factorial(n - 1)
8 | }
9 | }
10 |
11 | self.onmessage = ({data}) => {
12 | const {arr, start, end} = data
13 | for (let i = start; i < end; i++) {
14 | arr[i].y += factorial(
15 | Math.floor(Math.random() * 10) + 95
16 | )
17 | }
18 | self.postMessage(true)
19 | }
20 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/workers-module/vecIterModule.js:
--------------------------------------------------------------------------------
1 | import {vec} from "../../dist/index.js"
2 |
3 | console.log("vec worker init")
4 |
5 | const Position = vec({x: "num", y: "num", z: "num", w: "num"})
6 |
7 | function factorial(n) {
8 | if (n < 2) {
9 | return 1
10 | } else {
11 | return n * factorial(n - 1)
12 | }
13 | }
14 |
15 | const pos = new Position(1)
16 |
17 | self.onmessage = ({data}) => {
18 | const {memory, start, end} = data
19 | pos.memory = memory
20 | for (let i = start; i < end; i += 1) {
21 | pos.index(i).y += factorial(
22 | Math.floor(Math.random() * 10) + 95
23 | )
24 | }
25 | self.postMessage(true)
26 | }
27 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/workers-node/arrayIterModule.mjs:
--------------------------------------------------------------------------------
1 | import {parentPort} from "worker_threads"
2 |
3 | console.log("array worker init")
4 |
5 | function factorial(n) {
6 | if (n < 2) {
7 | return 1
8 | } else {
9 | return n * factorial(n - 1)
10 | }
11 | }
12 |
13 | parentPort.on("message", (data) => {
14 | const {arr, start, end} = data
15 | for (let i = start; i < end; i++) {
16 | arr[i].y += factorial(
17 | Math.floor(Math.random() * 10) + 95
18 | )
19 | }
20 | parentPort.postMessage(true)
21 | })
22 |
--------------------------------------------------------------------------------
/benchmarks/public/tests/workers-node/vecIterModule.mjs:
--------------------------------------------------------------------------------
1 | import {parentPort} from "worker_threads"
2 | import {vec} from "../../../../dist/index.js"
3 |
4 | console.log("vec worker init")
5 |
6 | const Position = vec({x: "num", y: "num", z: "num", w: "num"})
7 |
8 | function factorial(n) {
9 | if (n < 2) {
10 | return 1
11 | } else {
12 | return n * factorial(n - 1)
13 | }
14 | }
15 |
16 | parentPort.on("message", (data) => {
17 | const {memory, start, end} = data
18 | const pos = Position.fromMemory(memory)
19 | for (let i = start; i < end; i += 1) {
20 | pos.index(i).y += factorial(
21 | Math.floor(Math.random() * 10) + 95
22 | )
23 | }
24 | parentPort.postMessage(true)
25 | })
26 |
--------------------------------------------------------------------------------
/benchmarks/server.mjs:
--------------------------------------------------------------------------------
1 | import express from "express"
2 | import {dirname} from 'path'
3 | import {fileURLToPath} from 'url'
4 |
5 | const __filename = fileURLToPath(import.meta.url)
6 | const __dirname = dirname(__filename)
7 |
8 | const PORT = 8181
9 | const app = express()
10 |
11 | app.use(express.static(__dirname + "/public", {
12 | setHeaders: (res) => {
13 | res.setHeader("Cross-Origin-Opener-Policy", "same-origin")
14 | res.setHeader("Cross-Origin-Embedder-Policy", "require-corp")
15 | }
16 | }))
17 |
18 | app.use((req, res, next) => {
19 | console.info("[inbound request]: requested", req.originalUrl)
20 | res.setHeader("Cross-Origin-Opener-Policy", "same-origin")
21 | res.setHeader("Cross-Origin-Embedder-Policy", "require-corp")
22 | next()
23 | })
24 |
25 | app.listen(PORT, () => {
26 | console.info(`app is listening on: http://localhost:${PORT}`)
27 | })
28 |
--------------------------------------------------------------------------------
/buildInfo/index.js.br:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moomoolive/struct-vec/5675e9cd4427856171c239d2419815d906f75df6/buildInfo/index.js.br
--------------------------------------------------------------------------------
/cleanSingleLineComments.mjs:
--------------------------------------------------------------------------------
1 | import FileHound from 'filehound'
2 | import fs from 'fs'
3 | import {dirname} from 'path'
4 | import {fileURLToPath} from 'url'
5 |
6 | const targetFolder = process.argv[2].trim()
7 | const extension = process.argv[3]?.trim() === "--ts" ? "ts" : "js"
8 |
9 | console.info("transforming imports for", targetFolder, "to extension", extension)
10 |
11 | const __filename = fileURLToPath(import.meta.url)
12 | const __dirname = dirname(__filename)
13 |
14 | const files = FileHound.create()
15 | .paths(__dirname + targetFolder)
16 | .discard('node_modules')
17 | .ext(extension)
18 | .find()
19 |
20 |
21 | files.then((filePaths) => {
22 |
23 | filePaths.forEach((filepath) => {
24 | fs.readFile(filepath, 'utf8', (err, data) => {
25 | let newData = data.split("\n")
26 | .map(line => {
27 | const [target = ""] = line.split("//")
28 | return target
29 | })
30 | .filter(line => line.length > 0)
31 | .join("\n")
32 | if (err) {
33 | throw err
34 | }
35 |
36 | console.log(`writing to ${filepath}`)
37 | fs.writeFile(filepath, newData, function (err) {
38 | if (err) {
39 | throw err
40 | }
41 | console.log('complete')
42 | })
43 | })
44 |
45 | })
46 | })
--------------------------------------------------------------------------------
/codegenTests/codegen.mjs:
--------------------------------------------------------------------------------
1 | import {vecCompile} from "../dist/index.js"
2 | import fs from 'fs'
3 | import path from "path"
4 | import {dirname} from 'path'
5 | import {fileURLToPath} from 'url'
6 |
7 | const __filename = fileURLToPath(import.meta.url)
8 | const __dirname = dirname(__filename)
9 |
10 | const LIB_PATH = "../dist"
11 |
12 | export const structdef = {x: "bool", y: "f32", z: "char"}
13 |
14 | const namedJs = vecCompile(
15 | structdef,
16 | LIB_PATH,
17 | {lang: "js", exportSyntax: "named", className: "NamedJs"}
18 | )
19 | fs.writeFileSync(path.join(__dirname, "named-js.mjs"), namedJs, {
20 | encoding: "utf-8"
21 | })
22 |
23 | const namedTs = vecCompile(
24 | structdef,
25 | LIB_PATH,
26 | {lang: "ts", exportSyntax: "named", className: "NamedTs"}
27 | )
28 | fs.writeFileSync(path.join(__dirname, "named.ts"), namedTs, {
29 | encoding: "utf-8"
30 | })
31 |
32 | const defaultJs = vecCompile(
33 | structdef,
34 | LIB_PATH,
35 | {lang: "js", exportSyntax: "default", className: "DefaultJs"}
36 | )
37 | fs.writeFileSync(path.join(__dirname, "default-js.mjs"), defaultJs, {
38 | encoding: "utf-8"
39 | })
40 |
41 | const defaultTs = vecCompile(
42 | structdef,
43 | LIB_PATH,
44 | {lang: "ts", exportSyntax: "default", className: "DefaultTs"}
45 | )
46 | fs.writeFileSync(path.join(__dirname, "default.ts"), defaultTs, {
47 | encoding: "utf-8"
48 | })
49 |
50 | console.info("✅ successfully generated code samples for tests\n")
51 |
--------------------------------------------------------------------------------
/codegenTests/default-js.mjs:
--------------------------------------------------------------------------------
1 | import {Vec} from "../dist"
2 | /**
3 | * @extends {Vec<{"x":"bool","y":"f32","z":"char"}>}
4 | */
5 | class DefaultJs extends Vec {
6 | static def = {"x":"bool","y":"f32","z":"char"}
7 | static elementSize = 3
8 | static Cursor = class DefaultJsCursor {
9 |
10 | constructor(self, index) { this.self = self;this._viewingIndex = index}
11 | get y() { return this.self._f32Memory[this._viewingIndex] }; set y(newValue) { this.self._f32Memory[this._viewingIndex] = newValue };
12 |
13 | get z() { return String.fromCodePoint(this.self._i32Memory[this._viewingIndex + 1] || 32) }; set z(newValue) { this.self._i32Memory[this._viewingIndex + 1] = newValue.codePointAt(0) || 32 };
14 | get x() { return Boolean(this.self._i32Memory[this._viewingIndex + 2] & 1) }; set x(newValue) { this.self._i32Memory[this._viewingIndex + 2] &= -2;this.self._i32Memory[this._viewingIndex + 2] |= Boolean(newValue)};
15 | set e({y, z, x}) { this.y = y;this.z = z;this.x = x; }
16 | get e() { return {y: this.y, z: this.z, x: this.x} }
17 | get ref() { return new DefaultJs.Cursor(this.self, this._viewingIndex) }
18 | index(index) { this._viewingIndex = index * this.self.elementSize; return this }
19 | }
20 | get elementSize() { return 3 }
21 | get def() { return {"x":"bool","y":"f32","z":"char"} }
22 | get cursorDef() { return DefaultJs.Cursor }
23 | }
24 |
25 | export default {DefaultJs}
--------------------------------------------------------------------------------
/codegenTests/default.ts:
--------------------------------------------------------------------------------
1 | import {Vec, StructDef, Struct, CursorConstructor, VecCursor, DetachedVecCursor} from "../dist"
2 |
3 | class DefaultTs extends Vec<{"x":"bool","y":"f32","z":"char"}> {
4 | static readonly def: StructDef = {"x":"bool","y":"f32","z":"char"}
5 | static readonly elementSize: number = 3
6 | protected static Cursor = class DefaultTsCursor {
7 | _viewingIndex: number
8 | self: Vec<{"x":"bool","y":"f32","z":"char"}>
9 | constructor(self: Vec<{"x":"bool","y":"f32","z":"char"}>, index: number) { this.self = self;this._viewingIndex = index}
10 | get y(): number { return (this.self as unknown as {_f32Memory: Float32Array})._f32Memory[this._viewingIndex] }; set y(newValue: number) { (this.self as unknown as {_f32Memory: Float32Array})._f32Memory[this._viewingIndex] = newValue };
11 |
12 | get z(): string { return String.fromCodePoint((this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 1] || 32) }; set z(newValue: string) { (this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 1] = newValue.codePointAt(0) || 32 };
13 | get x(): boolean { return Boolean((this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 2] & 1) }; set x(newValue: boolean) { (this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 2] &= -2;(this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 2] |= (Boolean(newValue) as unknown as number)};
14 | set e({y, z, x}: Struct<{"x":"bool","y":"f32","z":"char"}>) { this.y = y;this.z = z;this.x = x; }
15 | get e(): Struct<{"x":"bool","y":"f32","z":"char"}> { return {y: this.y, z: this.z, x: this.x} }
16 | get ref(): VecCursor<{"x":"bool","y":"f32","z":"char"}> { return new DefaultTs.Cursor(this.self, this._viewingIndex) }
17 | index(index: number): DetachedVecCursor<{"x":"bool","y":"f32","z":"char"}> { this._viewingIndex = index * this.self.elementSize; return this }
18 | } as CursorConstructor<{"x":"bool","y":"f32","z":"char"}>
19 | get elementSize(): number { return 3 }
20 | get def(): StructDef { return {"x":"bool","y":"f32","z":"char"} }
21 | protected get cursorDef(): CursorConstructor<{"x":"bool","y":"f32","z":"char"}> { return DefaultTs.Cursor }
22 | }
23 |
24 | export default {DefaultTs}
--------------------------------------------------------------------------------
/codegenTests/jest.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | export default {
3 | preset: "ts-jest/presets/js-with-ts-esm",
4 | globals: {
5 | 'ts-jest': {useESM: true},
6 | },
7 | moduleNameMapper: {'^(\\.{1,2}/.*)\\.js$': '$1',},
8 | testEnvironment: "jsdom",
9 | verbose: true,
10 | modulePathIgnorePatterns: ["/node_modules", "/dist"]
11 | }
12 |
--------------------------------------------------------------------------------
/codegenTests/named-js.mjs:
--------------------------------------------------------------------------------
1 | import {Vec} from "../dist"
2 | /**
3 | * @extends {Vec<{"x":"bool","y":"f32","z":"char"}>}
4 | */
5 | export class NamedJs extends Vec {
6 | static def = {"x":"bool","y":"f32","z":"char"}
7 | static elementSize = 3
8 | static Cursor = class NamedJsCursor {
9 |
10 | constructor(self, index) { this.self = self;this._viewingIndex = index}
11 | get y() { return this.self._f32Memory[this._viewingIndex] }; set y(newValue) { this.self._f32Memory[this._viewingIndex] = newValue };
12 |
13 | get z() { return String.fromCodePoint(this.self._i32Memory[this._viewingIndex + 1] || 32) }; set z(newValue) { this.self._i32Memory[this._viewingIndex + 1] = newValue.codePointAt(0) || 32 };
14 | get x() { return Boolean(this.self._i32Memory[this._viewingIndex + 2] & 1) }; set x(newValue) { this.self._i32Memory[this._viewingIndex + 2] &= -2;this.self._i32Memory[this._viewingIndex + 2] |= Boolean(newValue)};
15 | set e({y, z, x}) { this.y = y;this.z = z;this.x = x; }
16 | get e() { return {y: this.y, z: this.z, x: this.x} }
17 | get ref() { return new NamedJs.Cursor(this.self, this._viewingIndex) }
18 | index(index) { this._viewingIndex = index * this.self.elementSize; return this }
19 | }
20 | get elementSize() { return 3 }
21 | get def() { return {"x":"bool","y":"f32","z":"char"} }
22 | get cursorDef() { return NamedJs.Cursor }
23 | }
--------------------------------------------------------------------------------
/codegenTests/named.ts:
--------------------------------------------------------------------------------
1 | import {Vec, StructDef, Struct, CursorConstructor, VecCursor, DetachedVecCursor} from "../dist"
2 |
3 | export class NamedTs extends Vec<{"x":"bool","y":"f32","z":"char"}> {
4 | static readonly def: StructDef = {"x":"bool","y":"f32","z":"char"}
5 | static readonly elementSize: number = 3
6 | protected static Cursor = class NamedTsCursor {
7 | _viewingIndex: number
8 | self: Vec<{"x":"bool","y":"f32","z":"char"}>
9 | constructor(self: Vec<{"x":"bool","y":"f32","z":"char"}>, index: number) { this.self = self;this._viewingIndex = index}
10 | get y(): number { return (this.self as unknown as {_f32Memory: Float32Array})._f32Memory[this._viewingIndex] }; set y(newValue: number) { (this.self as unknown as {_f32Memory: Float32Array})._f32Memory[this._viewingIndex] = newValue };
11 |
12 | get z(): string { return String.fromCodePoint((this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 1] || 32) }; set z(newValue: string) { (this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 1] = newValue.codePointAt(0) || 32 };
13 | get x(): boolean { return Boolean((this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 2] & 1) }; set x(newValue: boolean) { (this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 2] &= -2;(this.self as unknown as {_i32Memory: Int32Array})._i32Memory[this._viewingIndex + 2] |= (Boolean(newValue) as unknown as number)};
14 | set e({y, z, x}: Struct<{"x":"bool","y":"f32","z":"char"}>) { this.y = y;this.z = z;this.x = x; }
15 | get e(): Struct<{"x":"bool","y":"f32","z":"char"}> { return {y: this.y, z: this.z, x: this.x} }
16 | get ref(): VecCursor<{"x":"bool","y":"f32","z":"char"}> { return new NamedTs.Cursor(this.self, this._viewingIndex) }
17 | index(index: number): DetachedVecCursor<{"x":"bool","y":"f32","z":"char"}> { this._viewingIndex = index * this.self.elementSize; return this }
18 | } as CursorConstructor<{"x":"bool","y":"f32","z":"char"}>
19 | get elementSize(): number { return 3 }
20 | get def(): StructDef { return {"x":"bool","y":"f32","z":"char"} }
21 | protected get cursorDef(): CursorConstructor<{"x":"bool","y":"f32","z":"char"}> { return NamedTs.Cursor }
22 | }
--------------------------------------------------------------------------------
/dist-deno/compiler.ts:
--------------------------------------------------------------------------------
1 | import {StructDef, VALID_DATA_TYPES_INTERNAL, defaults} from "./core.ts"
2 | const ALLOW_CHARACTERS_IN_VARIABLE_NAME = /^[A-Za-z_\\$][A-Za-z0-9_\\$]*$/
3 | export const ERR_PREFIX = "[VecGenerator]"
4 | function validVariableName(candidate: string): boolean {
5 | return ALLOW_CHARACTERS_IN_VARIABLE_NAME.test(candidate)
6 | }
7 | type StructDefToken = {
8 | elementSize: number
9 | fieldNames: string[]
10 | float32Fields: {field: string, offset: number}[]
11 | int32Fields: {field: string, offset: number}[]
12 | booleanFields: {
13 | field: string
14 | offset: number
15 | byteOffset: number
16 | }[],
17 | charFields: {field: string, offset: number}[]
18 | }
19 | function validType(type: string): boolean {
20 | switch(type) {
21 | case "f32":
22 | case "i32":
23 | case "bool":
24 | case "char":
25 | return true
26 | default:
27 | return false
28 | }
29 | }
30 | function restrictedFieldName(name: string): boolean {
31 | switch(name) {
32 | case "self":
33 | case "e":
34 | case "_viewingIndex":
35 | case "ref":
36 | case "index":
37 | return true
38 | default:
39 | return false
40 | }
41 | }
42 | const BITS_IN_I32 = 32
43 | export function tokenizeStructDef(def: any): StructDefToken {
44 | if (typeof def !== "object" || def === null || Array.isArray(def)) {
45 | throw SyntaxError(`${ERR_PREFIX} inputted invalid struct def. Expected object in the form of '{"field1": "f32", "field2": "char", "field3": "bool"}' got ${JSON.stringify(def)}`)
46 | }
47 | const fieldDefs = Object.keys(def).map((key) => {
48 | return {field: key, type: def[key]}
49 | })
50 | if (fieldDefs.length < 1) {
51 | throw SyntaxError(`${ERR_PREFIX} struct definition must have at least one key`)
52 | }
53 | let elementSize = 0
54 | const tokens: StructDefToken = {
55 | elementSize: 0,
56 | fieldNames: [],
57 | float32Fields: [],
58 | int32Fields: [],
59 | booleanFields: [],
60 | charFields: []
61 | }
62 | const float32Types = []
63 | const int32Types = []
64 | const boolTypes = []
65 | const charTypes = []
66 | for (let i = 0; i < fieldDefs.length; i += 1) {
67 | const {field, type} = fieldDefs[i]
68 |
69 | if (typeof field !== "string" || !validVariableName(field)) {
70 | throw SyntaxError(`${ERR_PREFIX} Bracket notation is disallowed, all structDef must be indexable by dot notation. Field "${field}" of struct requires indexing as "vec['${field}']" which is disallowed. Consider removing any hyphens.`)
71 | } else if (restrictedFieldName(field)) {
72 | throw SyntaxError(`${ERR_PREFIX} field "${field}" is a reserved name.`)
73 | }
74 | if (typeof type !== "string") {
75 | throw SyntaxError(`${ERR_PREFIX} field "${field}" is not a string, got "${typeof type}". Struct definition field values must be a string of ${VALID_DATA_TYPES_INTERNAL.join(", ")}`)
76 | } else if (!validType(type)) {
77 | throw SyntaxError(`${ERR_PREFIX} field "${field}" is not a valid type (got type "${type}"). Struct definition fields can only be of type of ${VALID_DATA_TYPES_INTERNAL.join(", ")}`)
78 | }
79 | switch(type) {
80 | case "f32":
81 | float32Types.push(field)
82 | break
83 | case "i32":
84 | int32Types.push(field)
85 | break
86 | case "bool":
87 | boolTypes.push(field)
88 | break
89 | case "char":
90 | charTypes.push(field)
91 | break
92 | }
93 | }
94 |
95 | float32Types.sort()
96 | for (let i = 0; i < float32Types.length; i += 1) {
97 | const field = float32Types[i]
98 | tokens.fieldNames.push(field)
99 | tokens.float32Fields.push({
100 | field,
101 | offset: elementSize
102 | })
103 | elementSize += 1
104 | }
105 | int32Types.sort()
106 | for (let i = 0; i < int32Types.length; i += 1) {
107 | const field = int32Types[i]
108 | tokens.fieldNames.push(field)
109 | tokens.int32Fields.push({
110 | field,
111 | offset: elementSize
112 | })
113 | elementSize += 1
114 | }
115 | charTypes.sort()
116 | for (let i = 0; i < charTypes.length; i += 1) {
117 | const field = charTypes[i]
118 | tokens.fieldNames.push(field)
119 | tokens.charFields.push({
120 | field,
121 | offset: elementSize
122 | })
123 | elementSize += 1
124 | }
125 | boolTypes.sort()
126 | let start = 0
127 | while (start < boolTypes.length) {
128 | const boolsLeft = boolTypes.length - start
129 | const end = boolsLeft < BITS_IN_I32
130 | ? boolsLeft
131 | : BITS_IN_I32
132 | for (let i = start; i < start + end; i += 1) {
133 | const field = boolTypes[i]
134 | tokens.fieldNames.push(field)
135 | tokens.booleanFields.push({
136 | field,
137 | offset: elementSize,
138 | byteOffset: i - start
139 | })
140 | }
141 | elementSize += 1
142 | start += BITS_IN_I32
143 | }
144 | tokens.elementSize = elementSize
145 | return tokens
146 | }
147 | function reservedJsKeyword(word: string): boolean {
148 | switch(word) {
149 | case "false":
150 | case "true":
151 | case "null":
152 | case "await":
153 | case "static":
154 | case "public":
155 | case "protected":
156 | case "private":
157 | case "package":
158 | case "let":
159 | case "interface":
160 | case "implements":
161 | case "yield":
162 | case "with":
163 | case "while":
164 | case "void":
165 | case "var":
166 | case "typeof":
167 | case "try":
168 | case "throw":
169 | case "this":
170 | case "switch":
171 | case "super":
172 | case "return":
173 | case "new":
174 | case "instanceof":
175 | case "in":
176 | case "import":
177 | case "if":
178 | case "function":
179 | case "for":
180 | case "finally":
181 | case "extends":
182 | case "export":
183 | case "else":
184 | case "do":
185 | case "delete":
186 | case "default":
187 | case "debugger":
188 | case "continue":
189 | case "const":
190 | case "class":
191 | case "catch":
192 | case "case":
193 | case "break":
194 | return true
195 | default:
196 | return false
197 | }
198 | }
199 | export function invalidClassName(name: string): boolean {
200 | return (
201 | !validVariableName(name)
202 | || reservedJsKeyword(name)
203 | || name.length < 1
204 | )
205 | }
206 | export function validateCompileOptions(input: any) {
207 | if (
208 | typeof input !== "object"
209 | || input === null
210 | || Array.isArray(input)
211 | ) {
212 | throw TypeError(`input options must be of type "object", got type "${typeof input}"`)
213 | }
214 | if (
215 | typeof input.pathToLib !== "string"
216 | || !input.pathToLib
217 | ) {
218 | throw TypeError("option 'pathToLib' missing")
219 | }
220 | if (
221 | typeof input.className !== "string"
222 | || invalidClassName(input.className)
223 | ) {
224 | throw SyntaxError(`inputted class name is not a valid javascript class name, got "${input.className}"`)
225 | }
226 | switch(input.exportSyntax) {
227 | case "named":
228 | case "default":
229 | case "none":
230 | break
231 | default:
232 | throw TypeError("invalid export Syntax option. exportSyntax must be either 'none', 'named', or 'default', got '" + input.exportSyntax + "''")
233 | }
234 | if (input.lang !== "js" && input.lang !== "ts") {
235 | throw TypeError(`option "bindings" must be either "js" or "ts". Got "${input.bindings}"`)
236 | }
237 | }
238 | type DefOptions = {
239 | lang: "js" | "ts"
240 | pathToLib: string
241 | className: string
242 | exportSyntax: "none" | "named" | "default"
243 | runtimeCompile: boolean
244 | }
245 | export function createVecDef(
246 | tokens: StructDefToken,
247 | structDef: StructDef,
248 | {
249 | lang,
250 | pathToLib,
251 | className,
252 | exportSyntax,
253 | runtimeCompile
254 | }: DefOptions
255 | ): {def: string, className: string} {
256 | const {
257 | elementSize, fieldNames, float32Fields,
258 | booleanFields, charFields, int32Fields
259 | } = tokens
260 | const def = JSON.stringify(structDef)
261 | const ts = lang === "ts"
262 | const generic = `<${def}>`
263 | const libPath = `"${pathToLib}"`
264 | const importStatement = `import {Vec${
265 | ts ? ", StructDef, Struct, CursorConstructor, VecCursor, DetachedVecCursor" : ""
266 | }} from ${libPath}`
267 | const CursorConstructor = "CursorConstructor" + generic
268 | const memory = ts ?
269 | "(this.self as unknown as {_f32Memory: Float32Array})._f32Memory"
270 | : "this.self._f32Memory"
271 | const intMemory = ts ?
272 | "(this.self as unknown as {_i32Memory: Int32Array})._i32Memory"
273 | : "this.self._i32Memory"
274 | return {
275 | className,
276 | def: `
277 | ${pathToLib !== "none" ? importStatement : ""}
278 | ${ts || runtimeCompile
279 | ? ""
280 | : `/**
281 | * @extends {Vec${generic}}
282 | */`
283 | }
284 | ${
285 | exportSyntax === "named" ? "export " : ""
286 | }class ${className} extends Vec${ts ? generic : ""} {
287 | static ${ts ? "readonly " : ""}def${ts ? ": StructDef" : ""} = ${def}
288 | static ${ts ? "readonly " : ""}elementSize${ts ? ": number" : ""} = ${elementSize}
289 | ${ts ? "protected " : ""}static Cursor = class ${className}Cursor {
290 | ${
291 | ts ? `_viewingIndex: number\n\t\tself: Vec${generic}` : ""
292 | }
293 | constructor(self${
294 | ts ? ": Vec" + generic : ""
295 | }, index${
296 | ts ? ": number" : ""
297 | }) { this.self = self;this._viewingIndex = index}
298 | ${float32Fields.map(({field, offset}) => {
299 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString())
300 | const base = `${memory}[this._viewingIndex${fieldOffset}]`
301 | const type = ts ? ": number" : ""
302 | const getter = `get ${field}()${type} { return ${base} }`
303 | const setter = `set ${field}(newValue${type}) { ${base} = newValue }`
304 | return `${getter}; ${setter};`
305 | }).join("\n\t ")}
306 | ${int32Fields.map(({field, offset}) => {
307 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString())
308 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`
309 | const type = ts ? ": number" : ""
310 | const getter = `get ${field}()${type} { return ${base} }`
311 | const setter = `set ${field}(newValue${type}) { ${base} = newValue }`
312 | return `${getter}; ${setter};`
313 | }).join("\n\t ")}
314 | ${charFields.map(({field, offset}) => {
315 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString())
316 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`
317 | const type = ts ? ": string" : ""
318 | const getter = `get ${field}()${type} { return String.fromCodePoint(${base} || ${defaults.spaceCharacteCodePoint}) }`
319 | const setter = `set ${field}(newValue${type}) { ${base} = newValue.codePointAt(0) || ${defaults.spaceCharacteCodePoint} }`
320 | return `${getter}; ${setter};`
321 | }).join("\n\t ")}
322 | ${booleanFields.map(({field, offset, byteOffset}) => {
323 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString())
324 | const mask = 1 << byteOffset
325 | const reverseMask = ~mask
326 | const type = ts ? ": boolean" : ""
327 | const boolCast = ts ? "(Boolean(newValue) as unknown as number)" : "Boolean(newValue)"
328 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`
329 | const getter = `get ${field}()${type} { return Boolean(${base} & ${mask}) }`
330 | const setter = `set ${field}(newValue${type}) { ${base} &= ${reverseMask};${base} |= ${boolCast}${byteOffset < 1 ? "" : " << " + byteOffset.toString()}}`
331 | return `${getter}; ${setter};`
332 | }).join("\n\t ")}
333 | set e({${
334 | fieldNames.map((field) => field).join(", ")
335 | }}${
336 | ts ? ": Struct" + generic : ""
337 | }) { ${
338 | fieldNames.map((field) => {
339 | return "this." + field + " = " + field
340 | }).join(";")
341 | }; }
342 | get e()${
343 | ts ? ": Struct" + generic : ""
344 | } { return {${
345 | fieldNames.map((field) => {
346 | return field + ": this." + field
347 | }).join(", ")
348 | }} }
349 | get ref()${ts ? `: VecCursor${generic}`: ""} { return new ${className}.Cursor(this.self, this._viewingIndex) }
350 | index(index${ts ? ": number" : ""})${ts? `: DetachedVecCursor${generic}` : ""} { this._viewingIndex = index * this.self.elementSize; return this }
351 | }${ts ? " as " + CursorConstructor : ""}
352 | get elementSize()${ts ? ": number" : ""} { return ${elementSize} }
353 | get def()${ts ? ": StructDef" : ""} { return ${def} }
354 | ${ts ? "protected " : ""}get cursorDef()${ts ? ": " + CursorConstructor : ""} { return ${className}.Cursor }
355 | }
356 | ${exportSyntax === "default" ? `export default {${className}}`: ""}
357 | `.trim()
358 | }
359 | }
--------------------------------------------------------------------------------
/dist-deno/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @module vec-struct
3 | */
4 | import {Vec as BaseVec} from "./core.ts"
5 | import type {StructDef, Struct, ReadonlyInt32Array} from "./core.ts"
6 | import {tokenizeStructDef, ERR_PREFIX, createVecDef, validateCompileOptions, invalidClassName} from "./compiler.ts"
7 | export {Vec} from "./core.ts"
8 | export type {CursorConstructor, VecCursor, ReadonlyInt32Array} from "./core.ts"
9 | export type {DetachedVecCursor} from "./core.ts"
10 | export type {SortCompareCallback, MapCallback} from "./core.ts"
11 | export type {ForEachCallback, ReduceCallback} from "./core.ts"
12 | export type {MapvCallback, TruthyIterCallback} from "./core.ts"
13 | export type {i32, f32, char, bool} from "./core.ts"
14 | export type {VecPrimitive, Primitive} from "./core.ts"
15 | export type {Struct, StructDef,} from "./core.ts"
16 | /**
17 | * A helper function to validate an inputted struct
18 | * definition. If inputted struct def is valid
19 | * the function true, otherwise it will return
20 | * false.
21 | *
22 | * @param {any} def the struct definition to be validated
23 | * @returns {boolean}
24 | *
25 | * @example Basic Usage
26 | * ```js
27 | * import {validateStructDef} from "vec-struct.ts"
28 | *
29 | * console.log(validateStructDef(null))
30 | * console.log(validateStructDef(true))
31 | * console.log(validateStructDef("def"))
32 | * console.log(validateStructDef({x: "randomType"}))
33 | * console.log(validateStructDef({x: {y: "f32"}}))
34 | *
35 | * console.log(validateStructDef({x: "f32"}))
36 | * console.log(validateStructDef({code: "f32"}))
37 | * ```
38 | */
39 | export function validateStructDef(def: any): boolean {
40 | try {
41 | tokenizeStructDef(def)
42 | return true
43 | } catch {
44 | return false
45 | }
46 | }
47 | /**
48 | * A vec compiler that can be used at runtime.
49 | * Creates class definitions for growable array-like
50 | * data structure (known as a vector or vec for short) that
51 | * hold fixed-sized objects (known as structs) from
52 | * your inputted struct definition.
53 | *
54 | * Vecs are backed by SharedArrayBuffers and therefore
55 | * can be passed across threads with zero serialization
56 | * cost.
57 | *
58 | * SAFETY-NOTE: this compiler uses the unsafe `Function`
59 | * constructor internally. Use`vecCompile` if you
60 | * wish to avoid unsafe code. Do note, that `vecCompile`
61 | * can only be used at build time.
62 | *
63 | * NOTE: vecs carry fixed-sized, strongly-typed
64 | * elements that cannot be change once the class is
65 | * created, unlike normal arrays.
66 | *
67 | * @param {StructDef} structDef a type definition for the elements
68 | * to be carried by an instance of the generated vec
69 | * class
70 | * @param {Object} [options]
71 | * @param {string} [options.className=AnonymousVec] the value
72 | * of the generated class's `name` property. Useful for debugging
73 | * @returns {VecClass} A class that creates vecs which conform
74 | * to inputted def
75 | *
76 | * @example Basic Usage
77 | * ```js
78 | * import {vec} from "struct-vec.ts"
79 | *
80 | *
81 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
82 | *
83 | * const p = new PositionV()
84 | *
85 | * const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
86 | * const geo = new geoCoordinates(15).fill({latitude: 1, longitude: 1})
87 | *
88 | *
89 | * const errClass = vec(null)
90 | * const errClass2 = vec({x: "unknownType"})
91 | * ```
92 | */
93 | export function vec(
94 | structDef: S,
95 | options: {
96 | className?: string
97 | } = {}
98 | ): VecClass {
99 | if (typeof SharedArrayBuffer === "undefined") {
100 | throw new Error(`${ERR_PREFIX} sharedArrayBuffers are not supported in this environment and are required for vecs`)
101 | }
102 | const tokens = tokenizeStructDef(structDef)
103 | const {
104 | className = "AnonymousVec"
105 | } = options
106 | if (invalidClassName(className)) {
107 | throw SyntaxError(`inputted class name (className option) is not a valid javascript class name, got "${className}"`)
108 | }
109 | const {def, className: clsName} = createVecDef(tokens, structDef, {
110 | lang: "js",
111 | exportSyntax: "none",
112 | pathToLib: "none",
113 | className,
114 | runtimeCompile: true
115 | })
116 | const genericVec = Function(`"use strict";return (Vec) => {
117 | ${def}
118 | return ${clsName}
119 | }`)()(BaseVec)
120 | return genericVec
121 | }
122 | /**
123 | * A vec compiler that can be used at build time.
124 | * Creates class definitions for growable array-like
125 | * data structure (known as a vector or vec for short) that
126 | * hold fixed-sized objects (known as structs) from
127 | * your inputted struct definition.
128 | *
129 | * Class definitions created by this compiler are the exact same
130 | * as the one's created by the runtime compiler.
131 | *
132 | * Vecs are backed by SharedArrayBuffers and therefore
133 | * can be passed across threads with zero serialization
134 | * cost.
135 | *
136 | * NOTE: this compiler does not come will any command line
137 | * tool, so you as the user must decide how to generate
138 | * and store the vec classes emitted by this compiler.
139 | *
140 | * NOTE: vecs carry fixed-sized, strongly-typed
141 | * elements that cannot be change once the class is
142 | * created, unlike normal arrays.
143 | *
144 | * @param {StructDef} structDef a type definition for the elements
145 | * to be carried by an instance of the generated vec
146 | * class.
147 | * @param {string} pathToLib where the "struct-vec" library is located
148 | * use the full url if using compiler in web (without build tool)
149 | * or deno.
150 | * @param {Object} [options]
151 | * @param {("js" | "ts")} [options.bindings="js"] what language should vec class
152 | * be generated in. Choose either "js" (javascript) or
153 | * "ts" (typescript). Defaults to "js".
154 | * @param {("none" | "named" | "default")} [options.exportSyntax="none"] what es6 export
155 | * syntax should class be generated with. Choose either
156 | * "none" (no import statement with class), "named" (use
157 | * the "export" syntax), or "default" (use "export default"
158 | * syntax). Defaults to "none".
159 | * @param {string} [options.className=AnonymousVec] the name of the generated
160 | * vec class. Defaults to "AnonymousVec".
161 | * @returns {string} a string rendition of vec class
162 | *
163 | * @example Basic Usage
164 | * ```js
165 | * import fs from "fs.ts"
166 | * import {vecCompile} from "struct-vec.ts"
167 | *
168 | *
169 | *
170 | *
171 | * const LIB_PATH = "struct-vec"
172 | *
173 | *
174 | * const def = {x: "f32", y: "f32", z: "f32"}
175 | * const GeneratedClass = vecCompile(def, LIB_PATH, {
176 | *
177 | * lang: "ts",
178 | *
179 | *
180 | * exportSyntax: "default",
181 | * className: "GeneratedClass"
182 | * })
183 | * console.log(typeof GeneratedClass)
184 | *
185 | *
186 | * fs.writeFileSync("GeneratedClass.js", GeneratedClass, {
187 | * encoding: "utf-8"
188 | * })
189 | * ```
190 | */
191 | export function vecCompile(
192 | structDef: StructDef,
193 | pathToLib: string,
194 | options: Partial<{
195 | lang: "ts" | "js"
196 | exportSyntax: "none" | "named" | "default"
197 | className: string
198 | }> = {}
199 | ): string {
200 | const {
201 | lang = "js",
202 | exportSyntax = "none",
203 | className = "AnonymousVec"
204 | } = options
205 | const compilerArgs = {
206 | lang,
207 | pathToLib,
208 | className,
209 | exportSyntax,
210 | runtimeCompile: false
211 | }
212 | validateCompileOptions(compilerArgs)
213 | const tokens = tokenizeStructDef(structDef)
214 | const {def} = createVecDef(tokens, structDef, compilerArgs)
215 | return def
216 | }
217 | export interface VecClass {
218 | /**
219 | * The definition of an individual
220 | * struct (element) in a vec class.
221 | * @type {StructDef}
222 | */
223 | readonly def: StructDef
224 | /**
225 | * The amount of raw memory an individual
226 | * struct (element of a vec) requires for vecs of this class.
227 | * An individual block of memory corresponds to
228 | * 4 bytes (32-bits).
229 | *
230 | * For example if ```elementSize``` is 2, each struct
231 | * will take 8 bytes.
232 | *
233 | * @type {number}
234 | */
235 | readonly elementSize: number
236 | /**
237 | * Checks if input is a of Vec type.
238 | *
239 | * If using the static method on generated
240 | * class, it will check if input is of same
241 | * Vec Type of generated class.
242 | *
243 | * If using the
244 | * static method on the `Vec` class exported
245 | * from this package, then it will check if
246 | * input is of type `Vec` (more general).
247 | *
248 | * @param {any} candidate the value to test
249 | * @returns {boolean}
250 | *
251 | * @example Basic Usage
252 | * ```js
253 | * import {vec, Vec} from "struct-vec.ts"
254 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
255 | * const CatsV = vec({cuteness: "f32", isDangerous: "bool"})
256 | *
257 | * const cats = new CatsV()
258 | * const positions = new PositionsV()
259 | *
260 | *
261 | *
262 | * console.log(Vec.isVec(cats))
263 | * console.log(Vec.isVec(positions))
264 | *
265 | *
266 | *
267 | *
268 | *
269 | * console.log(CatsV.isVec(cats))
270 | * console.log(CatsV.isVec(positions))
271 | *
272 | * console.log(PositionV.isVec(cats))
273 | * console.log(PositionV.isVec(positions))
274 | * ```
275 | */
276 | isVec(candidate: any): boolean
277 | /**
278 | * @constructor
279 | * @param {number} [initialCapacity=15] the amount
280 | * of capacity to initialize vec with. Defaults to
281 | * 15.
282 | *
283 | * @example Basic Usage
284 | * ```js
285 | * import {vec} from "struct-vec.ts"
286 | *
287 | * const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
288 | *
289 | *
290 | * const withCapacity = new geoCoordinates(100)
291 | * const without = new geoCoordinates()
292 | * ```
293 | */
294 | new (initialCapacity?: number): BaseVec
295 | /**
296 | * An alternate constructor for vecs.
297 | * This constructor creates a vec from
298 | * another vec's memory.
299 | *
300 | * This constructor is particularly useful
301 | * when multithreading. One can send the memory
302 | * (```memory``` property) of a vec on one thread
303 | * to another, via ```postMessage``` and initialize
304 | * an identical vec on the receiving thread through
305 | * this constructor.
306 | *
307 | * Vec memory is backed by ```SharedArrayBuffer```s,
308 | * so sending it between workers and the main thread is
309 | * a zero-copy operation. In other words, vec memory
310 | * is always sent by reference when using the ```postMessage```
311 | * method of ```Worker```s.
312 | *
313 | * @param {ReadonlyFloat32Array} memory memory
314 | * of another Vec of the same kind
315 | * @returns {Vec} A new vec
316 | *
317 | * @example Multithreading
318 | * ```js
319 | *
320 | * import {vec} from "struct-vec.ts"
321 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
322 | * const positions = new PositionV(10_000).fill(
323 | * {x: 1, y: 1, z: 1}
324 | * )
325 | *
326 | * const worker = new Worker("worker.js")
327 | *
328 | * worker.postMessage(positions.memory)
329 | *
330 | *
331 | * import {vec} from "struct-vec.ts"
332 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
333 | *
334 | * self.onmessage = (message) => {
335 | * PositionV.fromMemory(message.data).forEach((pos) => {
336 | * pos.x += 1
337 | * pos.y += 2
338 | * pos.z += 3
339 | * })
340 | * }
341 | * ```
342 | */
343 | fromMemory(memory: ReadonlyInt32Array): BaseVec
344 | /**
345 | * An alternate constructor for vecs.
346 | * Creates a vec from inputted
347 | * array, if all elements of array are compliant
348 | * with struct def of given vec class.
349 | *
350 | * @param {Array>} structArray array
351 | * from which to construct the vec.
352 | * @returns {Vec} A new vec
353 | *
354 | * @example Basic Usage
355 | * ```js
356 | * import {vec, Vec} from "struct-vec.ts"
357 | *
358 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
359 | * const arr = new Array(15).fill({x: 1, y: 2, z: 3})
360 | *
361 | * const positions = PositionsV.fromArray(arr)
362 | * console.log(Vec.isVec(positions))
363 | * ```
364 | *
365 | */
366 | fromArray(array: Struct[]): BaseVec
367 | /**
368 | * An alternate constructor for vecs.
369 | * Creates a new vec instance from an inputted
370 | * string.
371 | *
372 | * String should be a stringified vec. One
373 | * can stringify any vec instance by calling the
374 | * ```toJSON``` method.
375 | *
376 | * @param {string} vecString a stringified vec
377 | * @returns {Vec} A new vec
378 | *
379 | * @example Basic Usage
380 | * ```js
381 | * import {vec, Vec} from "struct-vec.ts"
382 | *
383 | * const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
384 | *
385 | * const geo = new geoCoordinates(15).fill({
386 | latitude: 20.10,
387 | longitude: 76.52
388 | })
389 | * const string = JSON.stringify(geo)
390 | * const parsed = JSON.parse(string)
391 | *
392 | * const geoCopy = geoCoordinates.fromString(parsed)
393 | * console.log(Vec.isVec(geoCopy))
394 | * ```
395 | */
396 | fromString(vecString: string): BaseVec
397 | }
398 | export default {
399 | vec,
400 | Vec: BaseVec,
401 | validateStructDef,
402 | vecCompile
403 | }
--------------------------------------------------------------------------------
/dist/compiler.d.ts:
--------------------------------------------------------------------------------
1 | import { StructDef } from "./core";
2 | export declare const ERR_PREFIX = "[VecGenerator]";
3 | declare type StructDefToken = {
4 | elementSize: number;
5 | fieldNames: string[];
6 | float32Fields: {
7 | field: string;
8 | offset: number;
9 | }[];
10 | int32Fields: {
11 | field: string;
12 | offset: number;
13 | }[];
14 | booleanFields: {
15 | field: string;
16 | offset: number;
17 | byteOffset: number;
18 | }[];
19 | charFields: {
20 | field: string;
21 | offset: number;
22 | }[];
23 | };
24 | export declare function tokenizeStructDef(def: any): StructDefToken;
25 | export declare function invalidClassName(name: string): boolean;
26 | export declare function validateCompileOptions(input: any): void;
27 | declare type DefOptions = {
28 | lang: "js" | "ts";
29 | pathToLib: string;
30 | className: string;
31 | exportSyntax: "none" | "named" | "default";
32 | runtimeCompile: boolean;
33 | };
34 | export declare function createVecDef(tokens: StructDefToken, structDef: StructDef, { lang, pathToLib, className, exportSyntax, runtimeCompile }: DefOptions): {
35 | def: string;
36 | className: string;
37 | };
38 | export {};
39 | //# sourceMappingURL=compiler.d.ts.map
--------------------------------------------------------------------------------
/dist/compiler.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAsC,MAAM,QAAQ,CAAA;AAKrE,eAAO,MAAM,UAAU,mBAAmB,CAAA;AAM1C,aAAK,cAAc,GAAG;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,aAAa,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,EAAE,CAAA;IAChD,WAAW,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,EAAE,CAAA;IAC9C,aAAa,EAAE;QACX,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;QACd,UAAU,EAAE,MAAM,CAAA;KACrB,EAAE,CAAC;IACJ,UAAU,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,EAAE,CAAA;CAChD,CAAA;AA6BD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc,CA8G1D;AAuDD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMtD;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,GAAG,QAkChD;AAED,aAAK,UAAU,GAAG;IACd,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IAC1C,cAAc,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,wBAAgB,YAAY,CACxB,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,SAAS,EACpB,EACI,IAAI,EACJ,SAAS,EACT,SAAS,EACT,YAAY,EACZ,cAAc,EACjB,EAAE,UAAU,GACd;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,CAyGlC"}
--------------------------------------------------------------------------------
/dist/compiler.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.createVecDef = exports.validateCompileOptions = exports.invalidClassName = exports.tokenizeStructDef = exports.ERR_PREFIX = void 0;
4 | const core_1 = require("./core");
5 | // Allows for alpha-numeric characters, $, and _.
6 | // Numbers are not allow to be the first character.
7 | const ALLOW_CHARACTERS_IN_VARIABLE_NAME = /^[A-Za-z_\\$][A-Za-z0-9_\\$]*$/;
8 | exports.ERR_PREFIX = "[VecGenerator]";
9 | function validVariableName(candidate) {
10 | return ALLOW_CHARACTERS_IN_VARIABLE_NAME.test(candidate);
11 | }
12 | function validType(type) {
13 | switch (type) {
14 | case "f32":
15 | case "i32":
16 | case "bool":
17 | case "char":
18 | return true;
19 | default:
20 | return false;
21 | }
22 | }
23 | function restrictedFieldName(name) {
24 | switch (name) {
25 | case "self":
26 | case "e":
27 | case "_viewingIndex":
28 | case "ref":
29 | case "index":
30 | return true;
31 | default:
32 | return false;
33 | }
34 | }
35 | const BITS_IN_I32 = 32;
36 | function tokenizeStructDef(def) {
37 | if (typeof def !== "object" || def === null || Array.isArray(def)) {
38 | throw SyntaxError(`${exports.ERR_PREFIX} inputted invalid struct def. Expected object in the form of '{"field1": "f32", "field2": "char", "field3": "bool"}' got ${JSON.stringify(def)}`);
39 | }
40 | const fieldDefs = Object.keys(def).map((key) => {
41 | return { field: key, type: def[key] };
42 | });
43 | if (fieldDefs.length < 1) {
44 | throw SyntaxError(`${exports.ERR_PREFIX} struct definition must have at least one key`);
45 | }
46 | let elementSize = 0;
47 | const tokens = {
48 | elementSize: 0,
49 | fieldNames: [],
50 | float32Fields: [],
51 | int32Fields: [],
52 | booleanFields: [],
53 | charFields: []
54 | };
55 | const float32Types = [];
56 | const int32Types = [];
57 | const boolTypes = [];
58 | const charTypes = [];
59 | for (let i = 0; i < fieldDefs.length; i += 1) {
60 | const { field, type } = fieldDefs[i];
61 | if (typeof field !== "string" || !validVariableName(field)) {
62 | throw SyntaxError(`${exports.ERR_PREFIX} Bracket notation is disallowed, all structDef must be indexable by dot notation. Field "${field}" of struct requires indexing as "vec['${field}']" which is disallowed. Consider removing any hyphens.`);
63 | }
64 | else if (restrictedFieldName(field)) {
65 | throw SyntaxError(`${exports.ERR_PREFIX} field "${field}" is a reserved name.`);
66 | }
67 | if (typeof type !== "string") {
68 | throw SyntaxError(`${exports.ERR_PREFIX} field "${field}" is not a string, got "${typeof type}". Struct definition field values must be a string of ${core_1.VALID_DATA_TYPES_INTERNAL.join(", ")}`);
69 | }
70 | else if (!validType(type)) {
71 | throw SyntaxError(`${exports.ERR_PREFIX} field "${field}" is not a valid type (got type "${type}"). Struct definition fields can only be of type of ${core_1.VALID_DATA_TYPES_INTERNAL.join(", ")}`);
72 | }
73 | switch (type) {
74 | case "f32":
75 | float32Types.push(field);
76 | break;
77 | case "i32":
78 | int32Types.push(field);
79 | break;
80 | case "bool":
81 | boolTypes.push(field);
82 | break;
83 | case "char":
84 | charTypes.push(field);
85 | break;
86 | }
87 | }
88 | float32Types.sort();
89 | for (let i = 0; i < float32Types.length; i += 1) {
90 | const field = float32Types[i];
91 | tokens.fieldNames.push(field);
92 | tokens.float32Fields.push({
93 | field,
94 | offset: elementSize
95 | });
96 | elementSize += 1;
97 | }
98 | int32Types.sort();
99 | for (let i = 0; i < int32Types.length; i += 1) {
100 | const field = int32Types[i];
101 | tokens.fieldNames.push(field);
102 | tokens.int32Fields.push({
103 | field,
104 | offset: elementSize
105 | });
106 | elementSize += 1;
107 | }
108 | charTypes.sort();
109 | for (let i = 0; i < charTypes.length; i += 1) {
110 | const field = charTypes[i];
111 | tokens.fieldNames.push(field);
112 | tokens.charFields.push({
113 | field,
114 | offset: elementSize
115 | });
116 | elementSize += 1;
117 | }
118 | boolTypes.sort();
119 | let start = 0;
120 | while (start < boolTypes.length) {
121 | const boolsLeft = boolTypes.length - start;
122 | const end = boolsLeft < BITS_IN_I32
123 | ? boolsLeft
124 | : BITS_IN_I32;
125 | for (let i = start; i < start + end; i += 1) {
126 | const field = boolTypes[i];
127 | tokens.fieldNames.push(field);
128 | tokens.booleanFields.push({
129 | field,
130 | offset: elementSize,
131 | byteOffset: i - start
132 | });
133 | }
134 | elementSize += 1;
135 | start += BITS_IN_I32;
136 | }
137 | tokens.elementSize = elementSize;
138 | return tokens;
139 | }
140 | exports.tokenizeStructDef = tokenizeStructDef;
141 | function reservedJsKeyword(word) {
142 | switch (word) {
143 | case "false":
144 | case "true":
145 | case "null":
146 | case "await":
147 | case "static":
148 | case "public":
149 | case "protected":
150 | case "private":
151 | case "package":
152 | case "let":
153 | case "interface":
154 | case "implements":
155 | case "yield":
156 | case "with":
157 | case "while":
158 | case "void":
159 | case "var":
160 | case "typeof":
161 | case "try":
162 | case "throw":
163 | case "this":
164 | case "switch":
165 | case "super":
166 | case "return":
167 | case "new":
168 | case "instanceof":
169 | case "in":
170 | case "import":
171 | case "if":
172 | case "function":
173 | case "for":
174 | case "finally":
175 | case "extends":
176 | case "export":
177 | case "else":
178 | case "do":
179 | case "delete":
180 | case "default":
181 | case "debugger":
182 | case "continue":
183 | case "const":
184 | case "class":
185 | case "catch":
186 | case "case":
187 | case "break":
188 | return true;
189 | default:
190 | return false;
191 | }
192 | }
193 | function invalidClassName(name) {
194 | return (!validVariableName(name)
195 | || reservedJsKeyword(name)
196 | || name.length < 1);
197 | }
198 | exports.invalidClassName = invalidClassName;
199 | function validateCompileOptions(input) {
200 | if (typeof input !== "object"
201 | || input === null
202 | || Array.isArray(input)) {
203 | throw TypeError(`input options must be of type "object", got type "${typeof input}"`);
204 | }
205 | if (typeof input.pathToLib !== "string"
206 | || !input.pathToLib) {
207 | throw TypeError("option 'pathToLib' missing");
208 | }
209 | if (typeof input.className !== "string"
210 | || invalidClassName(input.className)) {
211 | throw SyntaxError(`inputted class name is not a valid javascript class name, got "${input.className}"`);
212 | }
213 | switch (input.exportSyntax) {
214 | case "named":
215 | case "default":
216 | case "none":
217 | break;
218 | default:
219 | throw TypeError("invalid export Syntax option. exportSyntax must be either 'none', 'named', or 'default', got '" + input.exportSyntax + "''");
220 | }
221 | if (input.lang !== "js" && input.lang !== "ts") {
222 | throw TypeError(`option "bindings" must be either "js" or "ts". Got "${input.bindings}"`);
223 | }
224 | }
225 | exports.validateCompileOptions = validateCompileOptions;
226 | function createVecDef(tokens, structDef, { lang, pathToLib, className, exportSyntax, runtimeCompile }) {
227 | const { elementSize, fieldNames, float32Fields, booleanFields, charFields, int32Fields } = tokens;
228 | const def = JSON.stringify(structDef);
229 | const ts = lang === "ts";
230 | const generic = `<${def}>`;
231 | const libPath = `"${pathToLib}"`;
232 | const importStatement = `import {Vec${ts ? ", StructDef, Struct, CursorConstructor, VecCursor, DetachedVecCursor" : ""}} from ${libPath}`;
233 | const CursorConstructor = "CursorConstructor" + generic;
234 | const memory = ts ?
235 | "(this.self as unknown as {_f32Memory: Float32Array})._f32Memory"
236 | : "this.self._f32Memory";
237 | const intMemory = ts ?
238 | "(this.self as unknown as {_i32Memory: Int32Array})._i32Memory"
239 | : "this.self._i32Memory";
240 | return {
241 | className,
242 | def: `
243 | ${pathToLib !== "none" ? importStatement : ""}
244 | ${ts || runtimeCompile
245 | ? ""
246 | : `/**
247 | * @extends {Vec${generic}}
248 | */`}
249 | ${exportSyntax === "named" ? "export " : ""}class ${className} extends Vec${ts ? generic : ""} {
250 | static ${ts ? "readonly " : ""}def${ts ? ": StructDef" : ""} = ${def}
251 | static ${ts ? "readonly " : ""}elementSize${ts ? ": number" : ""} = ${elementSize}
252 | ${ts ? "protected " : ""}static Cursor = class ${className}Cursor {
253 | ${ts ? `_viewingIndex: number\n\t\tself: Vec${generic}` : ""}
254 | constructor(self${ts ? ": Vec" + generic : ""}, index${ts ? ": number" : ""}) { this.self = self;this._viewingIndex = index}
255 | ${float32Fields.map(({ field, offset }) => {
256 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
257 | const base = `${memory}[this._viewingIndex${fieldOffset}]`;
258 | const type = ts ? ": number" : "";
259 | const getter = `get ${field}()${type} { return ${base} }`;
260 | const setter = `set ${field}(newValue${type}) { ${base} = newValue }`;
261 | return `${getter}; ${setter};`;
262 | }).join("\n\t ")}
263 | ${int32Fields.map(({ field, offset }) => {
264 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
265 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`;
266 | const type = ts ? ": number" : "";
267 | const getter = `get ${field}()${type} { return ${base} }`;
268 | const setter = `set ${field}(newValue${type}) { ${base} = newValue }`;
269 | return `${getter}; ${setter};`;
270 | }).join("\n\t ")}
271 | ${charFields.map(({ field, offset }) => {
272 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
273 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`;
274 | const type = ts ? ": string" : "";
275 | const getter = `get ${field}()${type} { return String.fromCodePoint(${base} || ${32 /* spaceCharacteCodePoint */}) }`;
276 | const setter = `set ${field}(newValue${type}) { ${base} = newValue.codePointAt(0) || ${32 /* spaceCharacteCodePoint */} }`;
277 | return `${getter}; ${setter};`;
278 | }).join("\n\t ")}
279 | ${booleanFields.map(({ field, offset, byteOffset }) => {
280 | const fieldOffset = offset < 1 ? "" : (" + " + offset.toString());
281 | const mask = 1 << byteOffset;
282 | const reverseMask = ~mask;
283 | const type = ts ? ": boolean" : "";
284 | const boolCast = ts ? "(Boolean(newValue) as unknown as number)" : "Boolean(newValue)";
285 | const base = `${intMemory}[this._viewingIndex${fieldOffset}]`;
286 | const getter = `get ${field}()${type} { return Boolean(${base} & ${mask}) }`;
287 | const setter = `set ${field}(newValue${type}) { ${base} &= ${reverseMask};${base} |= ${boolCast}${byteOffset < 1 ? "" : " << " + byteOffset.toString()}}`;
288 | return `${getter}; ${setter};`;
289 | }).join("\n\t ")}
290 | set e({${fieldNames.map((field) => field).join(", ")}}${ts ? ": Struct" + generic : ""}) { ${fieldNames.map((field) => {
291 | return "this." + field + " = " + field;
292 | }).join(";")}; }
293 | get e()${ts ? ": Struct" + generic : ""} { return {${fieldNames.map((field) => {
294 | return field + ": this." + field;
295 | }).join(", ")}} }
296 | get ref()${ts ? `: VecCursor${generic}` : ""} { return new ${className}.Cursor(this.self, this._viewingIndex) }
297 | index(index${ts ? ": number" : ""})${ts ? `: DetachedVecCursor${generic}` : ""} { this._viewingIndex = index * this.self.elementSize; return this }
298 | }${ts ? " as " + CursorConstructor : ""}
299 | get elementSize()${ts ? ": number" : ""} { return ${elementSize} }
300 | get def()${ts ? ": StructDef" : ""} { return ${def} }
301 | ${ts ? "protected " : ""}get cursorDef()${ts ? ": " + CursorConstructor : ""} { return ${className}.Cursor }
302 | }
303 |
304 | ${exportSyntax === "default" ? `export default {${className}}` : ""}
305 | `.trim()
306 | };
307 | }
308 | exports.createVecDef = createVecDef;
309 |
--------------------------------------------------------------------------------
/dist/core.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @module vec-struct
3 | */
4 | import { Vec as BaseVec } from "./core";
5 | import type { StructDef, Struct, ReadonlyInt32Array } from "./core";
6 | export { Vec } from "./core";
7 | export type { CursorConstructor, VecCursor, ReadonlyInt32Array } from "./core";
8 | export type { DetachedVecCursor } from "./core";
9 | export type { SortCompareCallback, MapCallback } from "./core";
10 | export type { ForEachCallback, ReduceCallback } from "./core";
11 | export type { MapvCallback, TruthyIterCallback } from "./core";
12 | export type { i32, f32, char, bool } from "./core";
13 | export type { VecPrimitive, Primitive } from "./core";
14 | export type { Struct, StructDef, } from "./core";
15 | /**
16 | * A helper function to validate an inputted struct
17 | * definition. If inputted struct def is valid
18 | * the function true, otherwise it will return
19 | * false.
20 | *
21 | * @param {any} def the struct definition to be validated
22 | * @returns {boolean}
23 | *
24 | * @example Basic Usage
25 | * ```js
26 | * import {validateStructDef} from "vec-struct"
27 | *
28 | * console.log(validateStructDef(null)) // output: false
29 | * console.log(validateStructDef(true)) // output: false
30 | * console.log(validateStructDef("def")) // output: false
31 | * console.log(validateStructDef({x: "randomType"})) // output: false
32 | * console.log(validateStructDef({x: {y: "f32"}})) // output: false
33 | *
34 | * console.log(validateStructDef({x: "f32"})) // output: true
35 | * console.log(validateStructDef({code: "f32"})) // output: true
36 | * ```
37 | */
38 | export declare function validateStructDef(def: any): boolean;
39 | /**
40 | * A vec compiler that can be used at runtime.
41 | * Creates class definitions for growable array-like
42 | * data structure (known as a vector or vec for short) that
43 | * hold fixed-sized objects (known as structs) from
44 | * your inputted struct definition.
45 | *
46 | * Vecs are backed by SharedArrayBuffers and therefore
47 | * can be passed across threads with zero serialization
48 | * cost.
49 | *
50 | * SAFETY-NOTE: this compiler uses the unsafe `Function`
51 | * constructor internally. Use`vecCompile` if you
52 | * wish to avoid unsafe code. Do note, that `vecCompile`
53 | * can only be used at build time.
54 | *
55 | * NOTE: vecs carry fixed-sized, strongly-typed
56 | * elements that cannot be change once the class is
57 | * created, unlike normal arrays.
58 | *
59 | * @param {StructDef} structDef a type definition for the elements
60 | * to be carried by an instance of the generated vec
61 | * class
62 | * @param {Object} [options]
63 | * @param {string} [options.className=AnonymousVec] the value
64 | * of the generated class's `name` property. Useful for debugging
65 | * @returns {VecClass} A class that creates vecs which conform
66 | * to inputted def
67 | *
68 | * @example Basic Usage
69 | * ```js
70 | * import {vec} from "struct-vec"
71 | *
72 | * // create Vec definition
73 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
74 | * // now initialize like a normal class
75 | * const p = new PositionV()
76 | *
77 | * const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
78 | * const geo = new geoCoordinates(15).fill({latitude: 1, longitude: 1})
79 | *
80 | * // invalid struct defs throws error
81 | * const errClass = vec(null) // SyntaxError
82 | * const errClass2 = vec({x: "unknownType"}) // SyntaxError
83 | * ```
84 | */
85 | export declare function vec(structDef: S, options?: {
86 | className?: string;
87 | }): VecClass;
88 | /**
89 | * A vec compiler that can be used at build time.
90 | * Creates class definitions for growable array-like
91 | * data structure (known as a vector or vec for short) that
92 | * hold fixed-sized objects (known as structs) from
93 | * your inputted struct definition.
94 | *
95 | * Class definitions created by this compiler are the exact same
96 | * as the one's created by the runtime compiler.
97 | *
98 | * Vecs are backed by SharedArrayBuffers and therefore
99 | * can be passed across threads with zero serialization
100 | * cost.
101 | *
102 | * NOTE: this compiler does not come will any command line
103 | * tool, so you as the user must decide how to generate
104 | * and store the vec classes emitted by this compiler.
105 | *
106 | * NOTE: vecs carry fixed-sized, strongly-typed
107 | * elements that cannot be change once the class is
108 | * created, unlike normal arrays.
109 | *
110 | * @param {StructDef} structDef a type definition for the elements
111 | * to be carried by an instance of the generated vec
112 | * class.
113 | * @param {string} pathToLib where the "struct-vec" library is located
114 | * use the full url if using compiler in web (without build tool)
115 | * or deno.
116 | * @param {Object} [options]
117 | * @param {("js" | "ts")} [options.bindings="js"] what language should vec class
118 | * be generated in. Choose either "js" (javascript) or
119 | * "ts" (typescript). Defaults to "js".
120 | * @param {("none" | "named" | "default")} [options.exportSyntax="none"] what es6 export
121 | * syntax should class be generated with. Choose either
122 | * "none" (no import statement with class), "named" (use
123 | * the "export" syntax), or "default" (use "export default"
124 | * syntax). Defaults to "none".
125 | * @param {string} [options.className=AnonymousVec] the name of the generated
126 | * vec class. Defaults to "AnonymousVec".
127 | * @returns {string} a string rendition of vec class
128 | *
129 | * @example Basic Usage
130 | * ```js
131 | * import fs from "fs"
132 | * import {vecCompile} from "struct-vec"
133 | *
134 | * // the path to the "struct-vec" library.
135 | * // For the web or deno, you would
136 | * // put the full url to the library.
137 | * const LIB_PATH = "struct-vec"
138 | *
139 | * // create Vec definition
140 | * const def = {x: "f32", y: "f32", z: "f32"}
141 | * const GeneratedClass = vecCompile(def, LIB_PATH, {
142 | * // create a typescript class
143 | * lang: "ts",
144 | * // export the class with "export default"
145 | * // syntax
146 | * exportSyntax: "default",
147 | * className: "GeneratedClass"
148 | * })
149 | * console.log(typeof GeneratedClass) // output: string
150 | * // write the class to disk to use later
151 | * // // or in another application
152 | * fs.writeFileSync("GeneratedClass.js", GeneratedClass, {
153 | * encoding: "utf-8"
154 | * })
155 | * ```
156 | */
157 | export declare function vecCompile(structDef: StructDef, pathToLib: string, options?: Partial<{
158 | lang: "ts" | "js";
159 | exportSyntax: "none" | "named" | "default";
160 | className: string;
161 | }>): string;
162 | export interface VecClass {
163 | /**
164 | * The definition of an individual
165 | * struct (element) in a vec class.
166 | * @type {StructDef}
167 | */
168 | readonly def: StructDef;
169 | /**
170 | * The amount of raw memory an individual
171 | * struct (element of a vec) requires for vecs of this class.
172 | * An individual block of memory corresponds to
173 | * 4 bytes (32-bits).
174 | *
175 | * For example if ```elementSize``` is 2, each struct
176 | * will take 8 bytes.
177 | *
178 | * @type {number}
179 | */
180 | readonly elementSize: number;
181 | /**
182 | * Checks if input is a of Vec type.
183 | *
184 | * If using the static method on generated
185 | * class, it will check if input is of same
186 | * Vec Type of generated class.
187 | *
188 | * If using the
189 | * static method on the `Vec` class exported
190 | * from this package, then it will check if
191 | * input is of type `Vec` (more general).
192 | *
193 | * @param {any} candidate the value to test
194 | * @returns {boolean}
195 | *
196 | * @example Basic Usage
197 | * ```js
198 | * import {vec, Vec} from "struct-vec"
199 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
200 | * const CatsV = vec({cuteness: "f32", isDangerous: "bool"})
201 | *
202 | * const cats = new CatsV()
203 | * const positions = new PositionsV()
204 | *
205 | * // base class method checks if
206 | * // input is a vec type
207 | * console.log(Vec.isVec(cats)) // output: true
208 | * console.log(Vec.isVec(positions)) // output: true
209 | *
210 | * // generated class method checks
211 | * // if input is the same Vec type
212 | * // as generated class
213 | * // equivalent to instanceof operator
214 | * console.log(CatsV.isVec(cats)) // output: true
215 | * console.log(CatsV.isVec(positions)) // output: false
216 | *
217 | * console.log(PositionV.isVec(cats)) // output: false
218 | * console.log(PositionV.isVec(positions)) // output: true
219 | * ```
220 | */
221 | isVec(candidate: any): boolean;
222 | /**
223 | * @constructor
224 | * @param {number} [initialCapacity=15] the amount
225 | * of capacity to initialize vec with. Defaults to
226 | * 15.
227 | *
228 | * @example Basic Usage
229 | * ```js
230 | * import {vec} from "struct-vec"
231 | *
232 | * const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
233 | *
234 | * // both are valid ways to initialize
235 | * const withCapacity = new geoCoordinates(100)
236 | * const without = new geoCoordinates()
237 | * ```
238 | */
239 | new (initialCapacity?: number): BaseVec;
240 | /**
241 | * An alternate constructor for vecs.
242 | * This constructor creates a vec from
243 | * another vec's memory.
244 | *
245 | * This constructor is particularly useful
246 | * when multithreading. One can send the memory
247 | * (```memory``` property) of a vec on one thread
248 | * to another, via ```postMessage``` and initialize
249 | * an identical vec on the receiving thread through
250 | * this constructor.
251 | *
252 | * Vec memory is backed by ```SharedArrayBuffer```s,
253 | * so sending it between workers and the main thread is
254 | * a zero-copy operation. In other words, vec memory
255 | * is always sent by reference when using the ```postMessage```
256 | * method of ```Worker```s.
257 | *
258 | * @param {ReadonlyFloat32Array} memory memory
259 | * of another Vec of the same kind
260 | * @returns {Vec} A new vec
261 | *
262 | * @example Multithreading
263 | * ```js
264 | * // ------------ index.js ---------------
265 | * import {vec} from "struct-vec"
266 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
267 | * const positions = new PositionV(10_000).fill(
268 | * {x: 1, y: 1, z: 1}
269 | * )
270 | *
271 | * const worker = new Worker("worker.js")
272 | * // pass by reference, no copying
273 | * worker.postMessage(positions.memory)
274 | *
275 | * // ------------ worker.js ---------------
276 | * import {vec} from "struct-vec" // or importScripts if in Firefox
277 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
278 | *
279 | * self.onmessage = (message) => {
280 | * PositionV.fromMemory(message.data).forEach((pos) => {
281 | * pos.x += 1
282 | * pos.y += 2
283 | * pos.z += 3
284 | * })
285 | * }
286 | * ```
287 | */
288 | fromMemory(memory: ReadonlyInt32Array): BaseVec;
289 | /**
290 | * An alternate constructor for vecs.
291 | * Creates a vec from inputted
292 | * array, if all elements of array are compliant
293 | * with struct def of given vec class.
294 | *
295 | * @param {Array>} structArray array
296 | * from which to construct the vec.
297 | * @returns {Vec} A new vec
298 | *
299 | * @example Basic Usage
300 | * ```js
301 | * import {vec, Vec} from "struct-vec"
302 | *
303 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
304 | * const arr = new Array(15).fill({x: 1, y: 2, z: 3})
305 | *
306 | * const positions = PositionsV.fromArray(arr)
307 | * console.log(Vec.isVec(positions)) // output: true
308 | * ```
309 | *
310 | */
311 | fromArray(array: Struct[]): BaseVec;
312 | /**
313 | * An alternate constructor for vecs.
314 | * Creates a new vec instance from an inputted
315 | * string.
316 | *
317 | * String should be a stringified vec. One
318 | * can stringify any vec instance by calling the
319 | * ```toJSON``` method.
320 | *
321 | * @param {string} vecString a stringified vec
322 | * @returns {Vec} A new vec
323 | *
324 | * @example Basic Usage
325 | * ```js
326 | * import {vec, Vec} from "struct-vec"
327 | *
328 | * const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
329 | *
330 | * const geo = new geoCoordinates(15).fill({
331 | latitude: 20.10,
332 | longitude: 76.52
333 | })
334 | * const string = JSON.stringify(geo)
335 | * const parsed = JSON.parse(string)
336 | *
337 | * const geoCopy = geoCoordinates.fromString(parsed)
338 | * console.log(Vec.isVec(geoCopy)) // output: true
339 | * ```
340 | */
341 | fromString(vecString: string): BaseVec;
342 | }
343 | declare const _default: {
344 | vec: typeof vec;
345 | Vec: typeof BaseVec;
346 | validateStructDef: typeof validateStructDef;
347 | vecCompile: typeof vecCompile;
348 | };
349 | export default _default;
350 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/dist/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAC,GAAG,IAAI,OAAO,EAAC,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAC,MAAM,QAAQ,CAAA;AAGjE,OAAO,EAAC,GAAG,EAAC,MAAM,QAAQ,CAAA;AAC1B,YAAY,EAAC,iBAAiB,EAAE,SAAS,EAAE,kBAAkB,EAAC,MAAM,QAAQ,CAAA;AAC5E,YAAY,EAAC,iBAAiB,EAAC,MAAM,QAAQ,CAAA;AAG7C,YAAY,EAAC,mBAAmB,EAAE,WAAW,EAAC,MAAM,QAAQ,CAAA;AAC5D,YAAY,EAAC,eAAe,EAAE,cAAc,EAAC,MAAM,QAAQ,CAAA;AAC3D,YAAY,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,QAAQ,CAAA;AAG5D,YAAY,EAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAC,MAAM,QAAQ,CAAA;AAChD,YAAY,EAAC,YAAY,EAAE,SAAS,EAAC,MAAM,QAAQ,CAAA;AACnD,YAAY,EAAC,MAAM,EAAE,SAAS,GAAE,MAAM,QAAQ,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAOnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,SAAS,EACnC,SAAS,EAAE,CAAC,EACZ,OAAO,GAAE;IACL,SAAS,CAAC,EAAE,MAAM,CAAA;CAChB,GACP,QAAQ,CAAC,CAAC,CAAC,CAuBb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wBAAgB,UAAU,CACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,OAAO,CAAC;IACb,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,YAAY,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IAC1C,SAAS,EAAE,MAAM,CAAA;CACpB,CAAM,GACR,MAAM,CAiBR;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,SAAS;IACzC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;IAEtB;;;;;;;;;;OAUG;IACJ,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,KAAK,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAA;IAC9B;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+CG;IACH,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAClD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CAC5C;;;;;;;AAED,wBAKC"}
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /**
3 | * @module vec-struct
4 | */
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.vecCompile = exports.vec = exports.validateStructDef = exports.Vec = void 0;
7 | // imports should be one line only => for build system
8 | const core_1 = require("./core");
9 | const compiler_1 = require("./compiler");
10 | var core_2 = require("./core");
11 | Object.defineProperty(exports, "Vec", { enumerable: true, get: function () { return core_2.Vec; } });
12 | /**
13 | * A helper function to validate an inputted struct
14 | * definition. If inputted struct def is valid
15 | * the function true, otherwise it will return
16 | * false.
17 | *
18 | * @param {any} def the struct definition to be validated
19 | * @returns {boolean}
20 | *
21 | * @example Basic Usage
22 | * ```js
23 | * import {validateStructDef} from "vec-struct"
24 | *
25 | * console.log(validateStructDef(null)) // output: false
26 | * console.log(validateStructDef(true)) // output: false
27 | * console.log(validateStructDef("def")) // output: false
28 | * console.log(validateStructDef({x: "randomType"})) // output: false
29 | * console.log(validateStructDef({x: {y: "f32"}})) // output: false
30 | *
31 | * console.log(validateStructDef({x: "f32"})) // output: true
32 | * console.log(validateStructDef({code: "f32"})) // output: true
33 | * ```
34 | */
35 | function validateStructDef(def) {
36 | try {
37 | (0, compiler_1.tokenizeStructDef)(def);
38 | return true;
39 | }
40 | catch (_a) {
41 | return false;
42 | }
43 | }
44 | exports.validateStructDef = validateStructDef;
45 | /**
46 | * A vec compiler that can be used at runtime.
47 | * Creates class definitions for growable array-like
48 | * data structure (known as a vector or vec for short) that
49 | * hold fixed-sized objects (known as structs) from
50 | * your inputted struct definition.
51 | *
52 | * Vecs are backed by SharedArrayBuffers and therefore
53 | * can be passed across threads with zero serialization
54 | * cost.
55 | *
56 | * SAFETY-NOTE: this compiler uses the unsafe `Function`
57 | * constructor internally. Use`vecCompile` if you
58 | * wish to avoid unsafe code. Do note, that `vecCompile`
59 | * can only be used at build time.
60 | *
61 | * NOTE: vecs carry fixed-sized, strongly-typed
62 | * elements that cannot be change once the class is
63 | * created, unlike normal arrays.
64 | *
65 | * @param {StructDef} structDef a type definition for the elements
66 | * to be carried by an instance of the generated vec
67 | * class
68 | * @param {Object} [options]
69 | * @param {string} [options.className=AnonymousVec] the value
70 | * of the generated class's `name` property. Useful for debugging
71 | * @returns {VecClass} A class that creates vecs which conform
72 | * to inputted def
73 | *
74 | * @example Basic Usage
75 | * ```js
76 | * import {vec} from "struct-vec"
77 | *
78 | * // create Vec definition
79 | * const PositionV = vec({x: "f32", y: "f32", z: "f32"})
80 | * // now initialize like a normal class
81 | * const p = new PositionV()
82 | *
83 | * const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
84 | * const geo = new geoCoordinates(15).fill({latitude: 1, longitude: 1})
85 | *
86 | * // invalid struct defs throws error
87 | * const errClass = vec(null) // SyntaxError
88 | * const errClass2 = vec({x: "unknownType"}) // SyntaxError
89 | * ```
90 | */
91 | function vec(structDef, options = {}) {
92 | if (typeof SharedArrayBuffer === "undefined") {
93 | throw new Error(`${compiler_1.ERR_PREFIX} sharedArrayBuffers are not supported in this environment and are required for vecs`);
94 | }
95 | const tokens = (0, compiler_1.tokenizeStructDef)(structDef);
96 | const { className = "AnonymousVec" } = options;
97 | if ((0, compiler_1.invalidClassName)(className)) {
98 | throw SyntaxError(`inputted class name (className option) is not a valid javascript class name, got "${className}"`);
99 | }
100 | const { def, className: clsName } = (0, compiler_1.createVecDef)(tokens, structDef, {
101 | lang: "js",
102 | exportSyntax: "none",
103 | pathToLib: "none",
104 | className,
105 | runtimeCompile: true
106 | });
107 | const genericVec = Function(`"use strict";return (Vec) => {
108 | ${def}
109 | return ${clsName}
110 | }`)()(core_1.Vec);
111 | return genericVec;
112 | }
113 | exports.vec = vec;
114 | /**
115 | * A vec compiler that can be used at build time.
116 | * Creates class definitions for growable array-like
117 | * data structure (known as a vector or vec for short) that
118 | * hold fixed-sized objects (known as structs) from
119 | * your inputted struct definition.
120 | *
121 | * Class definitions created by this compiler are the exact same
122 | * as the one's created by the runtime compiler.
123 | *
124 | * Vecs are backed by SharedArrayBuffers and therefore
125 | * can be passed across threads with zero serialization
126 | * cost.
127 | *
128 | * NOTE: this compiler does not come will any command line
129 | * tool, so you as the user must decide how to generate
130 | * and store the vec classes emitted by this compiler.
131 | *
132 | * NOTE: vecs carry fixed-sized, strongly-typed
133 | * elements that cannot be change once the class is
134 | * created, unlike normal arrays.
135 | *
136 | * @param {StructDef} structDef a type definition for the elements
137 | * to be carried by an instance of the generated vec
138 | * class.
139 | * @param {string} pathToLib where the "struct-vec" library is located
140 | * use the full url if using compiler in web (without build tool)
141 | * or deno.
142 | * @param {Object} [options]
143 | * @param {("js" | "ts")} [options.bindings="js"] what language should vec class
144 | * be generated in. Choose either "js" (javascript) or
145 | * "ts" (typescript). Defaults to "js".
146 | * @param {("none" | "named" | "default")} [options.exportSyntax="none"] what es6 export
147 | * syntax should class be generated with. Choose either
148 | * "none" (no import statement with class), "named" (use
149 | * the "export" syntax), or "default" (use "export default"
150 | * syntax). Defaults to "none".
151 | * @param {string} [options.className=AnonymousVec] the name of the generated
152 | * vec class. Defaults to "AnonymousVec".
153 | * @returns {string} a string rendition of vec class
154 | *
155 | * @example Basic Usage
156 | * ```js
157 | * import fs from "fs"
158 | * import {vecCompile} from "struct-vec"
159 | *
160 | * // the path to the "struct-vec" library.
161 | * // For the web or deno, you would
162 | * // put the full url to the library.
163 | * const LIB_PATH = "struct-vec"
164 | *
165 | * // create Vec definition
166 | * const def = {x: "f32", y: "f32", z: "f32"}
167 | * const GeneratedClass = vecCompile(def, LIB_PATH, {
168 | * // create a typescript class
169 | * lang: "ts",
170 | * // export the class with "export default"
171 | * // syntax
172 | * exportSyntax: "default",
173 | * className: "GeneratedClass"
174 | * })
175 | * console.log(typeof GeneratedClass) // output: string
176 | * // write the class to disk to use later
177 | * // // or in another application
178 | * fs.writeFileSync("GeneratedClass.js", GeneratedClass, {
179 | * encoding: "utf-8"
180 | * })
181 | * ```
182 | */
183 | function vecCompile(structDef, pathToLib, options = {}) {
184 | const { lang = "js", exportSyntax = "none", className = "AnonymousVec" } = options;
185 | const compilerArgs = {
186 | lang,
187 | pathToLib,
188 | className,
189 | exportSyntax,
190 | runtimeCompile: false
191 | };
192 | (0, compiler_1.validateCompileOptions)(compilerArgs);
193 | const tokens = (0, compiler_1.tokenizeStructDef)(structDef);
194 | const { def } = (0, compiler_1.createVecDef)(tokens, structDef, compilerArgs);
195 | return def;
196 | }
197 | exports.vecCompile = vecCompile;
198 | exports.default = {
199 | vec,
200 | Vec: core_1.Vec,
201 | validateStructDef,
202 | vecCompile
203 | };
204 |
--------------------------------------------------------------------------------
/jest.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | export default {
3 | preset: "ts-jest",
4 | testEnvironment: "jsdom",
5 | verbose: true,
6 | modulePathIgnorePatterns: ["/node_modules", "/dist"]
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "struct-vec",
3 | "version": "0.1.2",
4 | "description": "Javascript array-like data structures designed for multithreading",
5 | "main": "./dist/index.js",
6 | "types": "./dist/index.d.ts",
7 | "files": ["dist"],
8 | "scripts": {
9 | "build": "npm run test:lib && npm run build:all && npm run test:codegen && npm run post-build",
10 | "test:lib": "jest --runInBand ./src",
11 | "test:codegen": "node ./codegenTests/codegen.mjs && node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand --config=./codegenTests/jest.config.mjs ./codegenTests",
12 | "test:watch": "jest --runInBand --watchAll ./src",
13 | "test:dev": "jest --runInBand ./src/tests/references",
14 | "bench:node": "node ./benchmarks/public/index.mjs",
15 | "bench:web": "nodemon ./benchmarks/server.mjs",
16 | "bench:deno": "deno run --allow-read benchmarks/public/index.mjs",
17 | "prepare": "npm run build && husky install",
18 | "docs": "node ./scripts/docgen.mjs",
19 | "post-build": "npm run docs && npm run build:benchmark",
20 | "build:all": "npm run build:node && npm run build:web && npm run build:deno",
21 | "build:benchmark": "tsc --project scripts/tsconfig.benchmarks.json && node transformImports.mjs /benchmarks/public/dist",
22 | "build:web": "tsc --project scripts/tsconfig.web.json && node transformImports.mjs /__tmp__ && node ./scripts/webBuild.mjs",
23 | "build:node": "tsc",
24 | "build:deno": "node ./scripts/denoDistDir.mjs && node transformImports.mjs /dist-deno --ts && node cleanSingleLineComments.mjs /dist-deno --ts",
25 | "ci": "npm run test:lib && npm run build:node && npm run test:codegen",
26 | "pre-commit": "npm run build && git add -A"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/moomoolive/struct-vec.git"
31 | },
32 | "keywords": [],
33 | "author": "Mostafa Elbannan",
34 | "license": "MIT",
35 | "devDependencies": {
36 | "@jest/globals": "^27.5.1",
37 | "@luncheon/esbuild-plugin-gzip": "^0.1.0",
38 | "@types/jest": "^27.4.0",
39 | "@types/jsdoc-to-markdown": "^7.0.3",
40 | "esbuild": "^0.14.29",
41 | "express": "^4.17.3",
42 | "filehound": "^1.17.5",
43 | "husky": "^7.0.0",
44 | "jest": "^27.5.1",
45 | "jsdoc-to-markdown": "^7.1.1",
46 | "live-server": "^1.2.1",
47 | "nodemon": "^2.0.15",
48 | "recursive-copy": "^2.0.14",
49 | "ts-jest": "^27.1.3",
50 | "ts-node": "^10.5.0",
51 | "typescript": "^4.5.5"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/scripts/denoDistDir.mjs:
--------------------------------------------------------------------------------
1 | import copy from "recursive-copy"
2 | import fs from "fs"
3 |
4 | const options = {
5 | overwrite: true,
6 | filter: (path) => !/\.test\./gi.test(path)
7 | }
8 |
9 | copy(
10 | "src",
11 | "dist-deno",
12 | options,
13 | (err, res) => {
14 | if (err) {
15 | console.error("copy failed", err)
16 | } else {
17 | console.info("Copied", res.length, "files")
18 | fs.rmdirSync("dist-deno/tests")
19 | }
20 | })
--------------------------------------------------------------------------------
/scripts/distCopy.mjs:
--------------------------------------------------------------------------------
1 | import copy from "recursive-copy"
2 |
3 | const options = {overwrite: true}
4 |
5 | copy(
6 | "dist",
7 | "benchmarks/public/dist",
8 | options,
9 | (err, res) => {
10 | if (err) {
11 | console.error("copy failed", err)
12 | } else {
13 | console.info("Copied", res.length, "files")
14 | }
15 | })
16 |
17 | copy(
18 | "dist-web",
19 | "benchmarks/public/dist-web",
20 | options,
21 | (err, res) => {
22 | if (err) {
23 | console.error("copy failed", err)
24 | } else {
25 | console.info("Copied", res.length, "files")
26 | }
27 | })
28 |
--------------------------------------------------------------------------------
/scripts/docgen.mjs:
--------------------------------------------------------------------------------
1 | import fs from "fs"
2 | import path from "path"
3 | import jsdoc2md from "jsdoc-to-markdown"
4 |
5 | const API_REFERENCE_START = "## API Reference"
6 | const README_FILE_NAME = "README.md"
7 | const PATH = path.join(README_FILE_NAME)
8 |
9 | const data = jsdoc2md.getTemplateDataSync({files: "dist/*.js"})
10 | const rawDocs = jsdoc2md.renderSync({data})
11 | const importAndAnchorMutations = rawDocs
12 | .replace(
13 | /module_vec-struct..vec/gm,
14 | "module_vec-struct..vec_gen",
15 | )
16 | .replace(
17 | /from "struct-vec.js"/gm,
18 | `from "struct-vec"`
19 | )
20 | .replace(/\.js+/gm, "")
21 | const compiledModule = importAndAnchorMutations
22 | .split(/name="module_vec-struct"/gm)
23 | const target = compiledModule[compiledModule.length - 1]
24 | const [_, withoutHeader] = target.split("## vec-struct\n")
25 | const docs = (
26 | ""
27 | + "\n\n"
28 | + withoutHeader.trim()
29 | )
30 | console.info("📗 Docs were successfully generated")
31 |
32 | const readmeFile = fs.readFileSync(PATH, {encoding: "utf-8"})
33 | const [restOfDocs, _oldAPIRef] = readmeFile.split(
34 | API_REFERENCE_START
35 | )
36 |
37 | const newReadme = `${restOfDocs}${API_REFERENCE_START}\n${docs}`
38 | fs.writeFileSync(PATH, newReadme, {encoding: "utf-8"})
39 | console.info("✏️ Successfully updated docs")
40 |
--------------------------------------------------------------------------------
/scripts/tsconfig.benchmarks.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "ES6", /* Specify what module code is generated. */
28 | //"rootDir": "./src", /* Specify the root folder within your source files. */
29 | "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | "declaration": false, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | "declarationMap": false, /* Create sourcemaps for d.ts files. */
47 | //"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | "outDir": "../benchmarks/public/dist", /* Specify an output folder for all emitted files. */
51 | "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | },
101 | "include": ["../src/**/*"],
102 | "exclude": ["../src/tests"]
103 | }
104 |
--------------------------------------------------------------------------------
/scripts/tsconfig.web.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "ES6", /* Specify what module code is generated. */
28 | //"rootDir": "./src", /* Specify the root folder within your source files. */
29 | "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | "declaration": false, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | "declarationMap": false, /* Create sourcemaps for d.ts files. */
47 | //"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | "outDir": "../__tmp__", /* Specify an output folder for all emitted files. */
51 | "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | },
101 | "include": ["../src/**/*"],
102 | "exclude": ["../src/tests"]
103 | }
104 |
--------------------------------------------------------------------------------
/scripts/webBuild.mjs:
--------------------------------------------------------------------------------
1 | import fs from "fs"
2 | import esbuild from "esbuild"
3 | import gzipPlugin from "@luncheon/esbuild-plugin-gzip"
4 |
5 | esbuild.build({
6 | entryPoints: ["__tmp__/index.js"],
7 | minify: true,
8 | bundle: true,
9 | outfile: "dist-web/index.js",
10 | globalName: "structVec",
11 | platform: "browser",
12 | write: false,
13 | plugins: [
14 | gzipPlugin({
15 | brotli: true,
16 | gzip: false,
17 | onEnd: ({outputFiles}) => {
18 | const [_, zippedFile] = outputFiles
19 | const {path, contents} = zippedFile
20 | if (!fs.existsSync("buildInfo")) {
21 | fs.mkdirSync("buildInfo")
22 | }
23 | fs.writeFileSync("buildInfo/index.js.br", contents)
24 | fs.rmSync(path)
25 | }
26 | })
27 | ]
28 | })
--------------------------------------------------------------------------------
/src/tests/casting.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, it, describe} from "@jest/globals"
2 | import {vec, Vec} from "../index"
3 | const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
4 |
5 | describe("type checking", () => {
6 | it("Vec.isVec static method should correctly identify vecs and non-vecs", () => {
7 | expect(Vec.isVec(new geoCoordinates())).toBe(true)
8 | const p = vec({x: "f32", y: "f32", z: "f32"})
9 | expect(Vec.isVec(new p())).toBe(true)
10 |
11 | expect(Vec.isVec(0)).toBe(false)
12 | expect(Vec.isVec(null)).toBe(false)
13 | expect(Vec.isVec(undefined)).toBe(false)
14 | expect(Vec.isVec("hi")).toBe(false)
15 | expect(Vec.isVec(Symbol("vec"))).toBe(false)
16 | expect(Vec.isVec(true)).toBe(false)
17 | expect(Vec.isVec({})).toBe(false)
18 | expect(Vec.isVec([])).toBe(false)
19 | expect(Vec.isVec(() => {})).toBe(false)
20 | expect(Vec.isVec(class FakeVec {})).toBe(false)
21 | })
22 |
23 | it("Vec.isVec static method for generated class should check if type is instance of generated vec class", () => {
24 | expect(geoCoordinates.isVec(new geoCoordinates())).toBe(true)
25 |
26 | const p = vec({x: "f32", y: "f32", z: "f32"})
27 | expect(geoCoordinates.isVec(new p())).toBe(false)
28 | expect(geoCoordinates.isVec(0)).toBe(false)
29 | expect(geoCoordinates.isVec(null)).toBe(false)
30 | expect(geoCoordinates.isVec(undefined)).toBe(false)
31 | expect(geoCoordinates.isVec("hi")).toBe(false)
32 | expect(geoCoordinates.isVec(Symbol("vec"))).toBe(false)
33 | expect(geoCoordinates.isVec(true)).toBe(false)
34 | expect(geoCoordinates.isVec({})).toBe(false)
35 | expect(geoCoordinates.isVec([])).toBe(false)
36 | expect(geoCoordinates.isVec(() => {})).toBe(false)
37 | expect(geoCoordinates.isVec(class FakeVec {})).toBe(false)
38 | })
39 | })
40 |
41 | describe("casting between vec and array", () => {
42 | it("vec should be able to constructed from an array of compliant structs via 'fromArray' method", () => {
43 | const geoArray = [
44 | {latitude: 19.65, longitude: 89.22},
45 | {latitude: 19.65, longitude: 89.22},
46 | {latitude: 19.65, longitude: 89.22},
47 | {latitude: 19.65, longitude: 89.22},
48 | {latitude: 19.65, longitude: 89.22},
49 | ]
50 | expect(geoArray.length).toBe(5)
51 | const geoVec = geoCoordinates.fromArray(geoArray)
52 | expect(geoVec.length).toBe(5)
53 | geoVec.forEach((geo) => {
54 | expect(geo.e).toEqual({
55 | latitude: Math.fround(19.65),
56 | longitude: Math.fround(89.22)
57 | })
58 | })
59 | })
60 |
61 | it("Should be able to create array from vec via spread operator", () => {
62 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
63 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
64 | const target = [...vec1]
65 | expect(target).toEqual(new Array(5).fill({x: 1, y: 2, z: 3}))
66 | })
67 | })
68 |
69 | describe("casting between vec memory (float64Array) and vec", () => {
70 | it("should be able to construct vec from another vec's memory", () => {
71 | const Cats = vec({isCool: "f32", isDangerous: "f32"})
72 | const cats = new Cats().fill({isCool: 1, isDangerous: 1})
73 |
74 | const capacity = cats.capacity
75 | expect(cats.length).toBe(capacity)
76 | cats.forEach((cat) => {
77 | expect(cat.e).toEqual({isCool: 1, isDangerous: 1})
78 | })
79 | const newCats = Cats.fromMemory(cats.memory)
80 | expect(newCats.length).toBe(capacity)
81 | newCats.forEach((cat) => {
82 | expect(cat.e).toEqual({isCool: 1, isDangerous: 1})
83 | })
84 | })
85 |
86 | it("should be able to cast vec into float64array", () => {
87 | const Cats = vec({isCool: "f32", isDangerous: "f32"})
88 | const cats = new Cats().fill({isCool: 1, isDangerous: 1})
89 | expect(ArrayBuffer.isView(cats.memory)).toBe(true)
90 | })
91 | })
92 |
93 | describe("casting between string and vec", () => {
94 | it("vec can be transformed into a string", () => {
95 | const geo = new geoCoordinates(15).fill({
96 | latitude: 20.10,
97 | longitude: 76.52
98 | })
99 | expect(typeof geo.toJSON()).toBe("string")
100 | })
101 |
102 | it("vec can be transformed via JSON.stringify with no error", () => {
103 | const geo = new geoCoordinates(15).fill({
104 | latitude: 20.10,
105 | longitude: 76.52
106 | })
107 | const json = JSON.stringify(geo)
108 | expect(typeof json).toBe("string")
109 | })
110 |
111 | it("vec's stringified rendition can be parsed by JSON.parse with no errors", () => {
112 | const geo = new geoCoordinates(15).fill({
113 | latitude: 20.10,
114 | longitude: 76.52
115 | })
116 | const json = JSON.stringify(geo)
117 | expect(typeof json).toBe("string")
118 | expect(() => JSON.parse(json)).not.toThrow()
119 | })
120 |
121 | it("string rendition of vec can be casted to vec again", () => {
122 | const geo = new geoCoordinates(15).fill({
123 | latitude: 20.10,
124 | longitude: 76.52
125 | })
126 | geo.reserve(100)
127 | const string = JSON.stringify(geo)
128 | const parsed = JSON.parse(string)
129 | const geoCopy = geoCoordinates.fromString(parsed)
130 | expect(Vec.isVec(geoCopy)).toBe(true)
131 | expect(geoCopy.length).toBe(geo.length)
132 | expect(geoCopy.capacity).toBe(geoCopy.capacity)
133 | geo.forEach((coordinate, i) => {
134 | expect(coordinate.e).toEqual(
135 | geoCopy.index(i).e
136 | )
137 | })
138 | expect(true).toBe(true)
139 | })
140 |
141 | it("casting to string casts any NaNs to 0", () => {
142 | const geo = new geoCoordinates(15).fill({
143 | latitude: 20.10,
144 | longitude: 76.52
145 | })
146 | // @ts-ignore
147 | geo.index(0).latitude = "hi";geo.index(1).longitude = "hi";
148 | expect(geo.index(0).latitude).toBe(NaN)
149 | expect(geo.index(1).longitude).toBe(NaN)
150 | const str = geo.toJSON()
151 | const NaNsRemoved = geoCoordinates.fromString(str)
152 | expect(NaNsRemoved.index(0).latitude).toBe(0)
153 | expect(NaNsRemoved.index(1).longitude).toBe(0)
154 | })
155 | })
156 |
--------------------------------------------------------------------------------
/src/tests/compiler.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, it, describe} from "@jest/globals"
2 | import {vecCompile, StructDef} from "../index"
3 |
4 | describe("compiler throws error on wrong options", () => {
5 | it("invalid javascript class name inputted into className option throws error", () => {
6 | expect(() => {
7 | vecCompile({x: "char"}, "randompath", {
8 | className: "7890"
9 | })
10 | }).toThrow()
11 |
12 | expect(() => {
13 | vecCompile({x: "char"}, "randompath", {
14 | className: "for"
15 | })
16 | }).toThrow()
17 |
18 | expect(() => {
19 | vecCompile({x: "char"}, "randompath", {
20 | className: "await"
21 | })
22 | }).toThrow()
23 |
24 | expect(() => {
25 | vecCompile({x: "char"}, "randompath", {
26 | className: "🐩"
27 | })
28 | }).toThrow()
29 |
30 | expect(() => {
31 | vecCompile({x: "char"}, "randompath", {
32 | // @ts-ignore
33 | className: {}
34 | })
35 | }).toThrow()
36 |
37 | expect(() => {
38 | vecCompile({x: "char"}, "randompath", {
39 | // @ts-ignore
40 | className: []
41 | })
42 | }).toThrow()
43 |
44 | expect(() => {
45 | vecCompile({x: "char"}, "randompath", {
46 | // @ts-ignore
47 | className: ""
48 | })
49 | }).toThrow()
50 | })
51 |
52 | it("Inputting invalid 'exportSyntax' option throws error", () => {
53 | expect(() => {
54 | vecCompile({x: "char"}, "randompath", {
55 | //@ts-ignore
56 | exportSyntax: "7890"
57 | })
58 | }).toThrow()
59 |
60 | expect(() => {
61 | vecCompile({x: "char"}, "randompath", {
62 | //@ts-ignore
63 | exportSyntax: "export default"
64 | })
65 | }).toThrow()
66 |
67 | expect(() => {
68 | vecCompile({x: "char"}, "randompath", {
69 | //@ts-ignore
70 | exportSyntax: 1
71 | })
72 | }).toThrow()
73 | })
74 |
75 | it("Inputting invalid 'lang' option throws error", () => {
76 | expect(() => {
77 | vecCompile({x: "char"}, "randompath", {
78 | //@ts-ignore
79 | lang: "7890"
80 | })
81 | }).toThrow()
82 |
83 | expect(() => {
84 | vecCompile({x: "char"}, "randompath", {
85 | //@ts-ignore
86 | exportSyntax: "export default"
87 | })
88 | }).toThrow()
89 |
90 | expect(() => {
91 | vecCompile({x: "char"}, "randompath", {
92 | //@ts-ignore
93 | lang: []
94 | })
95 | }).toThrow()
96 | })
97 |
98 | it("not providing pathToCore argument throws error", () => {
99 | expect(() => {
100 | // @ts-ignore
101 | vecCompile({x: "char"})
102 | }).toThrow()
103 | })
104 |
105 | it("providing incorrect type to pathToCore argument throws error", () => {
106 | expect(() => {vecCompile({x: "char"}, null as unknown as string)}).toThrow()
107 | expect(() => {vecCompile({x: "char"}, true as unknown as string)}).toThrow()
108 | expect(() => {vecCompile({x: "char"}, false as unknown as string)}).toThrow()
109 | expect(() => {vecCompile({x: "char"}, 1 as unknown as string)}).toThrow()
110 | expect(() => {vecCompile({x: "char"}, 0 as unknown as string)}).toThrow()
111 | expect(() => {vecCompile({x: "char"}, {} as unknown as string)}).toThrow()
112 | expect(() => {vecCompile({x: "char"}, [] as unknown as string)}).toThrow()
113 | expect(() => {vecCompile({x: "char"}, Symbol() as unknown as string)}).toThrow()
114 | })
115 |
116 | it("providing empty string to pathToCore argument throws error", () => {
117 | expect(() => {vecCompile({x: "char"}, "")}).toThrow()
118 | })
119 |
120 | it("not providing struct def argument throws error", () => {
121 | expect(() => {
122 | // @ts-ignore
123 | vecCompile()
124 | }).toThrow()
125 | })
126 |
127 | it("providing incorrect type to struct def argument throws error", () => {
128 | expect(() => {vecCompile(true as unknown as StructDef, "hi")}).toThrow()
129 | expect(() => {vecCompile(undefined as unknown as StructDef, "hi")}).toThrow()
130 | expect(() => {vecCompile(null as unknown as StructDef, "hi")}).toThrow()
131 | expect(() => {vecCompile([] as unknown as StructDef, "hi")}).toThrow()
132 | expect(() => {vecCompile(Symbol() as unknown as StructDef, "hi")}).toThrow()
133 | expect(() => {vecCompile(1 as unknown as StructDef, "hi")}).toThrow()
134 | expect(() => {vecCompile(0 as unknown as StructDef, "hi")}).toThrow()
135 | })
136 |
137 | it("providing empty struct def argument throws error", () => {
138 | expect(() => {
139 | // @ts-ignore
140 | vecCompile({})
141 | }).toThrow()
142 | })
143 | })
144 |
145 | describe("compiler generates valid javascript", () => {
146 | it("javascript is valid", async () => {
147 | const def = vecCompile({x: "char"}, "../core.js", {
148 | lang: "js",
149 | })
150 | expect(typeof def).toBe("string")
151 | expect(true).toBe(true)
152 | })
153 | })
154 |
--------------------------------------------------------------------------------
/src/tests/indexing.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, it, describe} from "@jest/globals"
2 | import {vec} from "../index"
3 |
4 | describe("ways to index (and not index...lol)", () => {
5 | it("index can be targeted via array destructuring", () => {
6 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
7 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
8 | const [target] = vec1
9 | expect(target).toEqual({x: 1, y: 2, z: 3})
10 | })
11 |
12 | it("calling index without capturing (via .e, struct.yourField, etc.) in variable returns the vec and NOT a struct within the vec", () => {
13 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
14 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
15 | const index1 = vec1.index(1)
16 | expect(index1).not.toEqual({x: 1, y: 2, z: 3})
17 | })
18 |
19 | it("vec can only point at one value at a time", () => {
20 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
21 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
22 | const index1 = vec1.index(1)
23 | const index2 = vec1.index(2)
24 | expect(index2.e).toEqual(index1.e)
25 | })
26 | })
27 |
28 | describe("at method", () => {
29 | it("works like index method with positive integers", () => {
30 | const EmployeesV = vec({"salary": "f32", "department": "f32"})
31 | const employees = new EmployeesV()
32 |
33 | employees.push({salary: 100_000, department: 1})
34 | employees.push({salary: 153_020, department: 1})
35 | employees.push({salary: 103_122, department: 0})
36 | const firstElement = employees.index(0).e
37 | expect(firstElement).toEqual(employees.at(0).e)
38 |
39 | const secondElement = employees.index(1).e
40 | expect(secondElement).toEqual(employees.at(1).e)
41 |
42 | const thirdElement = employees.index(2).e
43 | expect(thirdElement).toEqual(employees.at(2).e)
44 | })
45 |
46 | it("computes reverse index with negative integers", () => {
47 | const EmployeesV = vec({"salary": "f32", "department": "f32"})
48 | const employees = new EmployeesV()
49 |
50 | employees.push({salary: 100_000, department: 1})
51 | employees.push({salary: 153_020, department: 1})
52 | employees.push({salary: 103_122, department: 0})
53 |
54 | const one = employees.index(employees.length - 1).e
55 | expect(one).toEqual(employees.at(-1).e)
56 |
57 | const two = employees.index(employees.length - 2).e
58 | expect(two).toEqual(employees.at(-2).e)
59 |
60 | const three = employees.index(employees.length - 3).e
61 | expect(three).toEqual(employees.at(-3)?.e)
62 | })
63 |
64 | it("indexes to 0 if input is -0", () => {
65 | const EmployeesV = vec({"salary": "f32", "department": "f32"})
66 | const employees = new EmployeesV()
67 |
68 | employees.push({salary: 100_000, department: 1})
69 | employees.push({salary: 153_020, department: 1})
70 | employees.push({salary: 103_122, department: 0})
71 | const firstElement = employees.at(-0).e
72 | expect(firstElement).toEqual(employees.index(0).e)
73 | })
74 | })
--------------------------------------------------------------------------------
/src/tests/iterators.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, it, describe} from "@jest/globals"
2 | import {vec} from "../index"
3 |
4 | describe("higher order iterators", () => {
5 | it("'forEach' iterator works as expected", () => {
6 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
7 | const teams = new SportsTeamV()
8 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
9 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
10 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
11 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
12 | teams.forEach((team) => team.pointsScored += 1)
13 |
14 | expect(teams.index(0).pointsScored).toBe(5)
15 | expect(teams.index(1).pointsScored).toBe(1)
16 | expect(teams.index(2).pointsScored).toBe(3)
17 | expect(teams.index(3).pointsScored).toBe(3)
18 | })
19 |
20 | it("'map' iterator works as expected", () => {
21 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
22 | const teams = new SportsTeamV()
23 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
24 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
25 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
26 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
27 | const powerRankings = teams.map((team) => team.powerRanking)
28 |
29 | expect(powerRankings).toEqual([1, 4, 2, 3])
30 | })
31 |
32 | it("'mapv' iterator work like 'map' except it returns a vec instead of any array", () => {
33 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
34 | const teams = new SportsTeamV()
35 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
36 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
37 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
38 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
39 | const powerRankings = teams.mapv((team) =>{
40 | team.playersOnRoster = 1
41 | team.pointsScored = 1
42 | team.powerRanking = 1
43 | return team
44 | })
45 |
46 | // make sure change is made
47 | expect([...powerRankings]).toEqual([
48 | {pointsScored: 1, powerRanking: 1, playersOnRoster: 1},
49 | {pointsScored: 1, powerRanking: 1, playersOnRoster: 1},
50 | {pointsScored: 1, powerRanking: 1, playersOnRoster: 1},
51 | {pointsScored: 1, powerRanking: 1, playersOnRoster: 1},
52 | ])
53 |
54 | // copy was returned and not same vec
55 | expect(powerRankings).not.toBe(teams)
56 | })
57 |
58 | it("'filter' iterator works as expected", () => {
59 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
60 | const teams = new SportsTeamV()
61 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
62 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
63 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
64 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
65 | const teamsThatDontSuck = teams.filter((team) => team.pointsScored > 0)
66 |
67 | expect(teamsThatDontSuck).toBeInstanceOf(SportsTeamV)
68 | expect(teamsThatDontSuck.length).toBe(3)
69 | expect(teamsThatDontSuck.index(0).e).toEqual({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
70 | expect(teamsThatDontSuck.index(1).e).toEqual({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
71 | expect(teamsThatDontSuck.index(2).e).toEqual({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
72 |
73 | const teamsThatAreGreat = teams.filter((team) => team.pointsScored > 2)
74 | expect(teamsThatAreGreat.length).toBe(1)
75 | expect(teamsThatAreGreat.index(0).e).toEqual({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
76 | })
77 |
78 | it("'find' iterator works as expected", () => {
79 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
80 | const teams = new SportsTeamV()
81 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
82 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
83 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
84 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
85 |
86 | const topTeam = teams.find((team) => team.powerRanking === 1)
87 | expect(topTeam?.e).toEqual({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
88 |
89 | const nonExistentTeam = teams.find((team) => team.powerRanking === 5)
90 | expect(nonExistentTeam).toBe(undefined)
91 | })
92 |
93 | it("'lastIndexOf' iterator works as expected", () => {
94 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
95 | const teams = new SportsTeamV()
96 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
97 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 14})
98 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
99 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
100 |
101 | const topTeamIndex = teams.lastIndexOf((team) => team.pointsScored === 2)
102 | expect(topTeamIndex).toEqual(3)
103 |
104 | const playersOnRosterLast = teams.lastIndexOf((team) => {
105 | return team.playersOnRoster === 14
106 | })
107 | expect(playersOnRosterLast).toBe(2)
108 |
109 | const nonExistentTeamIndex = teams.lastIndexOf((team) => team.powerRanking === 5)
110 | expect(nonExistentTeamIndex).toBe(-1)
111 | })
112 |
113 | it("'findIndex' iterator works as expected", () => {
114 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
115 | const teams = new SportsTeamV()
116 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
117 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
118 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
119 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
120 |
121 | const topTeamIndex = teams.findIndex((team) => team.powerRanking === 1)
122 | expect(topTeamIndex).toEqual(0)
123 |
124 | const nonExistentTeamIndex = teams.findIndex((team) => team.powerRanking === 5)
125 | expect(nonExistentTeamIndex).toBe(-1)
126 | })
127 |
128 | it("'reduce' iterator works as expected", () => {
129 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
130 | const teams = new SportsTeamV()
131 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
132 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
133 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
134 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
135 |
136 | const goalCount = teams.reduce((total, team) => {
137 | return total + team.pointsScored
138 | }, 0)
139 | expect(goalCount).toBe(8)
140 |
141 | const playerCount = teams.reduce((total, team) => total + team.playersOnRoster, 0)
142 | expect(playerCount).toBe(64)
143 | })
144 |
145 | it("'reduce' iterator should throw error with no initial value", () => {
146 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
147 | const teams = new SportsTeamV()
148 | // @ts-ignore
149 | expect(() => teams.reduce((total, val) => total + val.powerRanking)).toThrow()
150 | })
151 |
152 | it("'reduceRight' iterator works as expected and iterates the opposite ways as 'reduce' iterator", () => {
153 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
154 | const teams = new SportsTeamV()
155 | teams.push({pointsScored: 4, powerRanking: 1, playersOnRoster: 18})
156 | teams.push({pointsScored: 0, powerRanking: 4, playersOnRoster: 17})
157 | teams.push({pointsScored: 2, powerRanking: 2, playersOnRoster: 14})
158 | teams.push({pointsScored: 2, powerRanking: 3, playersOnRoster: 15})
159 |
160 | let goalCountIndexes = teams.length - 1
161 | const goalCount = teams.reduceRight((total, team, index) => {
162 | expect(index).toBe(goalCountIndexes)
163 | goalCountIndexes -= 1
164 | return total + team.pointsScored
165 | }, 0)
166 | expect(goalCount).toBe(8)
167 |
168 | let playerCountIndexes = teams.length - 1
169 | const playerCount = teams.reduceRight((total, team, index) => {
170 | expect(index).toBe(playerCountIndexes)
171 | playerCountIndexes -= 1
172 | return total + team.playersOnRoster
173 | }, 0)
174 | expect(playerCount).toBe(64)
175 | })
176 |
177 | it("'reduceRight' iterator should throw error with no initial value", () => {
178 | const SportsTeamV = vec({"pointsScored": "f32", "powerRanking": "f32", "playersOnRoster": "f32"})
179 | const teams = new SportsTeamV()
180 | // @ts-ignore
181 | expect(() => teams.reduceRight((total, val) => total + val.powerRanking)).toThrow()
182 | })
183 |
184 | it("'every' iterator works as expected", () => {
185 | const EmployeesV = vec({"salary": "f32", "department": "f32"})
186 | const employees = new EmployeesV()
187 |
188 | employees.push({salary: 100_000, department: 1})
189 | employees.push({salary: 153_020, department: 1})
190 | employees.push({salary: 103_122, department: 0})
191 |
192 | expect(employees.every((e) => e.salary > 99_999)).toBe(true)
193 | expect(employees.every((e) => e.salary > 199_999)).toBe(false)
194 | })
195 |
196 | it("'some' iterator works as expected", () => {
197 | const AliensV = vec({"height": "f32", "weight": "f32", "power": "f32"})
198 | const aliens = new AliensV()
199 |
200 | aliens.push({height: 8, weight: 200, power: 2})
201 | aliens.push({height: 11, weight: 187, power: 4})
202 | aliens.push({height: 10, weight: 200, power: 6})
203 | aliens.push({height: 13, weight: 203, power: 1})
204 |
205 | expect(aliens.some((alien) => alien.height > 12)).toBe(true)
206 | expect(aliens.some((alien) => alien.power === 4)).toBe(true)
207 | expect(aliens.some((alien) => alien.weight < 150)).toBe(false)
208 | expect(aliens.some((alien) => alien.height > 20)).toBe(false)
209 | })
210 | })
211 |
212 | describe("es6 iterators", () => {
213 | it("should be able to iterate over vec with 'for...of' syntax", () => {
214 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
215 |
216 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
217 | expect(true).toBe(true)
218 | let iterCalled = 0
219 | for (const element of vec1) {
220 | iterCalled += 1
221 | expect(element).toEqual({x: 1, y: 2, z: 3})
222 | }
223 | expect(iterCalled).toBe(vec1.length)
224 | })
225 |
226 | it("'entries' iterator works as expected", () => {
227 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
228 |
229 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
230 | expect(true).toBe(true)
231 | let iterCalled = 0
232 | for (const [index, element] of vec1.entries()) {
233 | expect(index).toBe(iterCalled)
234 | expect(element).toEqual({x: 1, y: 2, z: 3})
235 | iterCalled += 1
236 | }
237 | expect(iterCalled).toBe(vec1.length)
238 | })
239 |
240 | it("'values' iterator works as expected", () => {
241 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
242 |
243 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
244 | expect(true).toBe(true)
245 | let iterCalled = 0
246 | for (const value of vec1.values()) {
247 | expect(value).toEqual({x: 1, y: 2, z: 3})
248 | iterCalled += 1
249 | }
250 | expect(iterCalled).toBe(vec1.length)
251 | })
252 |
253 | it("'keys' iterator works as expected", () => {
254 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
255 |
256 | const vec1 = new PositionV(5).fill({x: 1, y: 2, z: 3})
257 | expect(true).toBe(true)
258 | let iterCalled = 0
259 | for (const key of vec1.keys()) {
260 | expect(key).toBe(iterCalled)
261 | iterCalled += 1
262 | }
263 | expect(iterCalled).toBe(vec1.length)
264 | })
265 | })
--------------------------------------------------------------------------------
/src/tests/memory.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, it, describe} from "@jest/globals"
2 | import {vec} from "../index"
3 |
4 | const Cats = vec({isDangerous: "f32", isCool: "f32"})
5 |
6 | describe("automatic memory management", () => {
7 | it("automatically resizes if capacity is at maximum", () => {
8 | const PositionVec = vec({x: "f32", y: "f32", z: "f32"})
9 | const positions = new PositionVec(1)
10 |
11 | expect(positions.length).toBe(0)
12 | expect(positions.capacity).toBe(1)
13 | positions.push({x: 1, y: 5, z: 1})
14 | expect(positions.length).toBe(1)
15 | expect(positions.capacity).toBe(1)
16 |
17 | let currentCapacity = 1
18 | positions.push({x: 1, y: 5, z: 1})
19 | expect(positions.length).toBe(2)
20 | expect(positions.capacity).toBeGreaterThan(currentCapacity)
21 | currentCapacity = positions.capacity
22 | positions.push({x: 1, y: 5, z: 1})
23 | positions.push({x: 1, y: 5, z: 1})
24 | positions.push({x: 1, y: 5, z: 1})
25 | positions.push({x: 1, y: 5, z: 1})
26 | expect(positions.length).toBe(6)
27 | expect(positions.capacity).toBeGreaterThan(currentCapacity)
28 | })
29 |
30 | it("automatically deallocates memory on the next removal action (eg. pop, shift, etc.) if capacity is 50 elements greater than length", () => {
31 | const cats = new Cats(1)
32 | cats.push({isDangerous: 3, isCool: 4})
33 | expect(cats.length).toBe(1)
34 | expect(cats.capacity).toBe(1)
35 |
36 | cats.reserve(10_000)
37 |
38 | const expectedCapacity = 10_001
39 | expect(cats.length).toBe(1)
40 | expect(cats.capacity).toBe(expectedCapacity)
41 |
42 | cats.pop()
43 | expect(cats.length).toBe(0)
44 | expect(cats.capacity).toBe(50)
45 | })
46 | })
47 |
48 | describe("manual memory management", () => {
49 | it("vecs from the same class can swap memory and not cause corruption", () => {
50 | const cats = new Cats().fill({isCool: 1, isDangerous: 1})
51 | const catties = new Cats().fill({isCool: 2, isDangerous: 2})
52 |
53 | const capacity = cats.capacity
54 | expect(cats.length).toBe(capacity)
55 | expect(catties.length).toBe(capacity)
56 |
57 | cats.forEach((cat) => {
58 | expect(cat.e).toEqual({isCool: 1, isDangerous: 1})
59 | })
60 | catties.forEach((cat) => {
61 | expect(cat.e).toEqual({isCool: 2, isDangerous: 2})
62 | })
63 |
64 | const catsMemory = cats.memory
65 | cats.memory = catties.memory
66 | catties.memory = catsMemory
67 |
68 | expect(cats.length).toBe(capacity)
69 | expect(catties.length).toBe(capacity)
70 |
71 | catties.forEach((cat) => {
72 | expect(cat.e).toEqual({isCool: 1, isDangerous: 1})
73 | })
74 | cats.forEach((cat) => {
75 | expect(cat.e).toEqual({isCool: 2, isDangerous: 2})
76 | })
77 | })
78 |
79 | it("shrinkTo method shrinks vec to inputted capcity or no-op if capcity is smaller than input", () => {
80 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
81 | const p = new PositionV(5)
82 |
83 | expect(p.capacity).toBe(5)
84 | p.shrinkTo(5)
85 | expect(p.capacity).toBe(5)
86 |
87 | p.shrinkTo(3)
88 | expect(p.capacity).toBe(3)
89 |
90 | p.fill({x: 1, y: 1, z: 1})
91 | p.shrinkTo(1)
92 | expect(p.capacity).toBe(3)
93 |
94 | p.reserve(100)
95 | expect(p.capacity).toBe(103)
96 | for (let i = 0; i < 3; i += 1) {
97 | expect(p.index(i).e).toEqual({x: 1, y: 1, z: 1})
98 | }
99 |
100 | p.shrinkTo(0)
101 | expect(p.capacity).toBe(3)
102 | for (let i = 0; i < 3; i += 1) {
103 | expect(p.index(i).e).toEqual({x: 1, y: 1, z: 1})
104 | }
105 | })
106 |
107 | it("reserve method should only expand capacity of array if length + additional is larger than current capacity", () => {
108 | const PositionV = vec({"x": "f32", "y": "f32", "z": "f32"})
109 | const vec1 = new PositionV(5)
110 |
111 | expect(vec1.capacity).toBe(5)
112 | vec1.reserve(5)
113 | expect(vec1.capacity).toBe(5)
114 |
115 | vec1.reserve(3)
116 | expect(vec1.capacity).toBe(5)
117 |
118 | vec1.reserve(100)
119 | expect(vec1.capacity).toBe(100)
120 |
121 | vec1.reserve(101)
122 | expect(vec1.capacity).toBe(101)
123 |
124 | vec1.fill({x: 1, y: 1, z: 1}, 0, 50)
125 | expect(vec1.length).toBe(50)
126 |
127 | vec1.reserve(50)
128 | expect(vec1.capacity).toBe(101)
129 |
130 | vec1.reserve(52)
131 | expect(vec1.capacity).toBe(102)
132 | expect(vec1.length).toBe(50)
133 | for (let i = 0; i < 50; i += 1) {
134 | expect(vec1.index(i).e).toEqual({x: 1, y: 1, z: 1})
135 | }
136 | })
137 |
138 | it("Vecs can be constructed from another vecs memory", () => {
139 | const cats = new Cats().fill({isCool: 1, isDangerous: 1})
140 |
141 | const capacity = cats.capacity
142 | expect(cats.length).toBe(capacity)
143 | cats.forEach((cat) => {
144 | expect(cat.e).toEqual({isCool: 1, isDangerous: 1})
145 | })
146 | const newCats = Cats.fromMemory(cats.memory)
147 | expect(newCats.length).toBe(capacity)
148 | newCats.forEach((cat) => {
149 | expect(cat.e).toEqual({isCool: 1, isDangerous: 1})
150 | })
151 | })
152 | })
153 |
154 | describe("memory protection", () => {
155 | it("attempting to overwrite cursor results in error", () => {
156 | const cats = new Cats()
157 | cats.push({isDangerous: 1, isCool: 3})
158 | expect(() => {
159 | //@ts-ignore
160 | cats.index(0) = {
161 | isDangerous: 2, isCool: 4
162 | }
163 | }).toThrow()
164 | })
165 |
166 | it("attempting to set length results in error", () => {
167 | const cats = new Cats()
168 | cats.push({isDangerous: 1, isCool: 3})
169 | //@ts-ignore
170 | expect(() => cats.length = 1).toThrow()
171 | })
172 |
173 | it("attempting to set capacity results in error", () => {
174 | const cats = new Cats()
175 | cats.push({isDangerous: 1, isCool: 3})
176 | //@ts-ignore
177 | expect(() => cats.capacity = 1).toThrow()
178 | })
179 |
180 | it("attempting to create vec with negative initial capacity creates vec with a capacity of the absolute value of input", () => {
181 | expect(new Cats(-1).capacity).toBe(1)
182 | expect(new Cats(-15).capacity).toBe(15)
183 | expect(new Cats(-120).capacity).toBe(120)
184 | })
185 | })
186 |
--------------------------------------------------------------------------------
/src/tests/naming.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, it, describe} from "@jest/globals"
2 | import {vec, StructDef} from "../index"
3 |
4 | describe("field name restrictions", () => {
5 | it("defining struct fields that require bracket indexing should throw error", () => {
6 | const invalidKeys = (key: string) => {
7 | return () => {return vec({[key]: "f32"})}
8 | }
9 | expect(invalidKeys("bracket-notation-require")).toThrow()
10 | expect(invalidKeys("@types")).toThrow()
11 | expect(invalidKeys("my#tag")).toThrow()
12 | expect(invalidKeys("a key with spaces")).toThrow()
13 | expect(invalidKeys("(bracket-within-bracket-key!!)")).toThrow()
14 | expect(invalidKeys("😈")).toThrow()
15 | })
16 |
17 | it("defining structs that include field names which conflict with any of the standard methods or properties throws error", () => {
18 | expect(() => {vec({e: "f32"})}).toThrow()
19 | expect(() => {vec({self: "f32"})}).toThrow()
20 | expect(() => {vec({ref: "f32"})}).toThrow()
21 | expect(() => {vec({_viewingIndex: "f32"})}).toThrow()
22 | expect(() => {vec({index: "f32"})}).toThrow()
23 | })
24 | })
25 |
26 | describe("class naming restrictions", () => {
27 | it("if class name is a js reserved keyword an error should be thrown", () => {
28 | expect(() => {vec({a: "i32"}, {className: "await"})}).toThrow()
29 | expect(() => {vec({a: "i32"}, {className: "for"})}).toThrow()
30 | expect(() => {vec({a: "i32"}, {className: "let"})}).toThrow()
31 | expect(() => {vec({a: "i32"}, {className: "const"})}).toThrow()
32 | expect(() => {vec({a: "i32"}, {className: "while"})}).toThrow()
33 | expect(() => {vec({a: "i32"}, {className: "if"})}).toThrow()
34 | })
35 |
36 | it("if class name does not follow naming convention for variables an error should be thrown", () => {
37 | expect(() => {vec({a: "i32"}, {className: "my class"})}).toThrow()
38 | expect(() => {vec({a: "i32"}, {className: "my-class"})}).toThrow()
39 | expect(() => {vec({a: "i32"}, {className: "1class"})}).toThrow()
40 | expect(() => {vec({a: "i32"}, {className: "cl@ss"})}).toThrow()
41 | expect(() => {vec({a: "i32"}, {className: "#class"})}).toThrow()
42 | expect(() => {vec({a: "i32"}, {className: "class&"})}).toThrow()
43 | expect(() => {vec({a: "i32"}, {className: "class*"})}).toThrow()
44 | })
45 |
46 | it("if class name has unicode characters an error should be thrown", () => {
47 | expect(() => {vec({a: "i32"}, {className: "myclass😙"})}).toThrow()
48 | expect(() => {vec({a: "i32"}, {className: "my🏊♂️class"})}).toThrow()
49 | expect(() => {vec({a: "i32"}, {className: "❤️class"})}).toThrow()
50 | expect(() => {vec({a: "i32"}, {className: "cl💀ss"})}).toThrow()
51 | expect(() => {vec({a: "i32"}, {className: "🔥class"})}).toThrow()
52 | expect(() => {vec({a: "i32"}, {className: "class😂"})}).toThrow()
53 | expect(() => {vec({a: "i32"}, {className: "✔️"})}).toThrow()
54 | })
55 | })
56 |
57 | describe("schema restrictions", () => {
58 | it("inputting a non-object (including arrays & null) into schema field throws error", () => {
59 | expect(() => {vec(null as unknown as StructDef)}).toThrow()
60 | expect(() => {vec([] as unknown as StructDef)}).toThrow()
61 | expect(() => {vec("my string" as unknown as StructDef)}).toThrow()
62 | expect(() => {vec(true as unknown as StructDef)}).toThrow()
63 | expect(() => {vec(Symbol("sym") as unknown as StructDef)}).toThrow()
64 | expect(() => {vec(undefined as unknown as StructDef)}).toThrow()
65 | expect(() => {vec(3 as unknown as StructDef)}).toThrow()
66 | })
67 |
68 | it("inputting non-string into struct field definition throws error", () => {
69 | expect(() => {vec({field1: null} as unknown as StructDef)}).toThrow()
70 | expect(() => {vec({field1: 1} as unknown as StructDef)}).toThrow()
71 | expect(() => {vec({field1: true} as unknown as StructDef)}).toThrow()
72 | expect(() => {vec({field1: Symbol("value")} as unknown as StructDef)}).toThrow()
73 | expect(() => {vec({field1: undefined} as unknown as StructDef)}).toThrow()
74 | expect(() => {vec({field1: {}} as unknown as StructDef)}).toThrow()
75 | expect(() => {vec({field1: []} as unknown as StructDef)}).toThrow()
76 | })
77 |
78 | it("inputting unsupported datatypes into struct field definition throws error", () => {
79 | expect(() => {vec({field1: "xtype"} as unknown as StructDef)}).toThrow()
80 | expect(() => {vec({field1: "aldjalfdjasf "} as unknown as StructDef)}).toThrow()
81 | expect(() => {vec({field1: "f64"} as unknown as StructDef)}).toThrow()
82 | expect(() => {vec({field1: "character"} as unknown as StructDef)}).toThrow()
83 | expect(() => {vec({field1: "boolean"} as unknown as StructDef)}).toThrow()
84 | expect(() => {vec({field1: "number"} as unknown as StructDef)}).toThrow()
85 | expect(() => {vec({field1: "my custom datatype"} as unknown as StructDef)}).toThrow()
86 | })
87 | })
88 |
--------------------------------------------------------------------------------
/src/tests/references.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, it, describe} from "@jest/globals"
2 | import {vec} from "../index"
3 |
4 | describe("references are independent from vec cursor", () => {
5 | it("can point to different index", () => {
6 | const Enemy = vec({power: "i32", isDead: "bool"})
7 | const orcs = new Enemy()
8 | orcs.push(
9 | {power: 55, isDead: false},
10 | {power: 13, isDead: false},
11 | {power: 72, isDead: false},
12 | )
13 | const ref = orcs.index(0).ref
14 | expect(orcs.index(1).e).toEqual({
15 | power: 13,
16 | isDead: false
17 | })
18 | expect(orcs.index(2).e).toEqual({
19 | power: 72,
20 | isDead: false
21 | })
22 | expect(ref.e).toEqual({
23 | power: 55,
24 | isDead: false
25 | })
26 | })
27 |
28 | it("can mutate different indexes without influencing cursor's index", () => {
29 | const Enemy = vec({power: "i32", isDead: "bool"})
30 | const orcs = new Enemy()
31 | orcs.push(
32 | {power: 55, isDead: false},
33 | {power: 13, isDead: false},
34 | {power: 72, isDead: false},
35 | )
36 | const orc1 = orcs.index(0).ref
37 | orc1.isDead = true
38 | orc1.power = 0
39 | expect(orcs.index(1).e).toEqual({
40 | power: 13,
41 | isDead: false
42 | })
43 | expect(orcs.index(2).e).toEqual({
44 | power: 72,
45 | isDead: false
46 | })
47 | expect(orc1.e).toEqual({
48 | power: 0,
49 | isDead: true
50 | })
51 | })
52 |
53 | it("multiple references pointing to the same memory can be created", () => {
54 | const Enemy = vec({power: "i32", isDead: "bool"})
55 | const orcs = new Enemy()
56 | orcs.push(
57 | {power: 55, isDead: false},
58 | {power: 13, isDead: false},
59 | {power: 72, isDead: false},
60 | )
61 | const ref1 = orcs.index(0).ref
62 | ref1.isDead = true
63 | ref1.power = 0
64 | expect(ref1.e).toEqual({
65 | power: 0,
66 | isDead: true
67 | })
68 | const ref2 = ref1.ref
69 | expect(ref2.e).toEqual({
70 | power: 0,
71 | isDead: true
72 | })
73 | ref2.isDead = false
74 | ref2.power = 10
75 | expect(ref2.e).toEqual({
76 | power: 10,
77 | isDead: false
78 | })
79 | expect(ref1.e).toEqual({
80 | power: 10,
81 | isDead: false
82 | })
83 | })
84 | })
85 |
86 | describe("references point to an index not a particular element", () => {
87 | it("ref does not point to a particular element", () => {
88 | const Enemy = vec({power: "i32", isDead: "bool"})
89 | const orcs = new Enemy()
90 | orcs.push(
91 | {power: 55, isDead: false},
92 | {power: 13, isDead: false},
93 | {power: 72, isDead: false},
94 | )
95 | const ref = orcs.index(0).ref
96 | expect(ref.e).toEqual({
97 | power: 55, isDead: false
98 | })
99 | orcs.swap(0, 1)
100 | expect(ref.e).not.toEqual({
101 | power: 55, isDead: false
102 | })
103 | expect(ref.e).toEqual({
104 | power: 13, isDead: false
105 | })
106 | })
107 | })
108 |
109 |
110 | describe("detached references", () => {
111 | it("detached references are just like normal references, except they can move positions", () => {
112 | const Enemy = vec({power: "i32", isDead: "bool"})
113 | const orcs = new Enemy()
114 | orcs.push(
115 | {power: 55, isDead: false},
116 | {power: 13, isDead: false},
117 | {power: 72, isDead: false},
118 | )
119 | const cursor = orcs.detachedCursor(0)
120 | expect(orcs.index(1).e).toEqual({
121 | power: 13,
122 | isDead: false
123 | })
124 | expect(orcs.index(2).e).toEqual({
125 | power: 72,
126 | isDead: false
127 | })
128 | expect(cursor.e).toEqual({
129 | power: 55,
130 | isDead: false
131 | })
132 | expect(cursor.index(2).e).toEqual({
133 | power: 72,
134 | isDead: false
135 | })
136 | expect(orcs.index(1).e).toEqual({
137 | power: 13,
138 | isDead: false
139 | })
140 | cursor.index(1).power = 9_001
141 | expect(orcs.index(1).e).toEqual({
142 | power: 9_001,
143 | isDead: false
144 | })
145 | })
146 | })
--------------------------------------------------------------------------------
/transformImports.mjs:
--------------------------------------------------------------------------------
1 | import FileHound from 'filehound'
2 | import fs from 'fs'
3 | import {dirname} from 'path'
4 | import {fileURLToPath} from 'url'
5 |
6 | const targetFolder = process.argv[2].trim()
7 | const extension = process.argv[3]?.trim() === "--ts" ? "ts" : "js"
8 |
9 | console.info("transforming imports for", targetFolder, "to extension", extension)
10 |
11 | const __filename = fileURLToPath(import.meta.url)
12 | const __dirname = dirname(__filename)
13 |
14 | const files = FileHound.create()
15 | .paths(__dirname + targetFolder)
16 | .discard('node_modules')
17 | .ext(extension)
18 | .find()
19 |
20 |
21 | files.then((filePaths) => {
22 |
23 | filePaths.forEach((filepath) => {
24 | fs.readFile(filepath, 'utf8', (err, data) => {
25 |
26 |
27 | if (!data.match(/import .* from/g)) {
28 | return
29 | }
30 | let newData = data
31 | .replace(/(import .* from\s+['"])(.*)(?=['"])/g, `$1$2.${extension}`)
32 | .replace(/(export .* from\s+['"])(.*)(?=['"])/g, `$1$2.${extension}`)
33 |
34 | if (err) throw err
35 |
36 | console.log(`writing to ${filepath}`)
37 | fs.writeFile(filepath, newData, function (err) {
38 | if (err) {
39 | throw err
40 | }
41 | console.log('complete')
42 | })
43 | })
44 |
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "CommonJS", /* Specify what module code is generated. */
28 | //"rootDir": "./src", /* Specify the root folder within your source files. */
29 | "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | //"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | "outDir": "./dist", /* Specify an output folder for all emitted files. */
51 | //"removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | },
101 | "include": ["src/**/*"],
102 | "exclude": ["src/tests"]
103 | }
104 |
--------------------------------------------------------------------------------