├── .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":"AAAA;;GAEG;AAYH,0BAAkB,QAAQ;IACtB,QAAQ,KAAK;IACb,qBAAqB,KAAK;IAC1B,sBAAsB,KAAK;CAC9B;AAKD,eAAO,MAAM,yBAAyB,yCAK5B,CAAA;AAEV;;;;;;;;;;;;;;;;;;;;GAoBG;AACF,qBAAa,GAAG,CAAC,CAAC,SAAS,SAAS;IACjC;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAK;IAEnC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAI;IAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAuCE;IACH,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO;IAIrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+CG;IACH,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,SAAS,EACjC,MAAM,EAAE,kBAAkB,GAC3B,GAAG,CAAC,CAAC,CAAC;IAIT;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,SAAS,EAChC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GACzB,GAAG,CAAC,CAAC,CAAC;IAMT;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,SAAS,EACjC,SAAS,EAAE,MAAM,GAClB,GAAG,CAAC,CAAC,CAAC;IA0BT,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAQ;IAEzB;;;;;;;;;;;;;;;;OAgBG;gBAEC,eAAe,GAAE,MAA0B,EAC3C,MAAM,CAAC,EAAE,kBAAkB;IAoB/B;;;;;;;;;;OAUG;IACF,IAAI,WAAW,IAAI,MAAM,CAEzB;IAED;;;;;OAKG;IACH,IAAI,GAAG,IAAI,SAAS,CAEnB;IAED,SAAS,KAAK,SAAS,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAI9C;IAED,OAAO,KAAK,MAAM,GAEjB;IAED;;;;;;;;OAQG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+DG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;;;;;;;;OASG;IACH,IAAI,MAAM,IAAI,kBAAkB,CAK/B;IAED,IAAI,MAAM,CAAC,SAAS,EAAE,kBAAkB,EAKvC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAKlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAS/B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAO,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC;IAUpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE;IAYxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAgBvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAwB/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS;IAc/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;IACH,SAAS,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM;IAclD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,WAAW,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM;IAcpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,MAAM,CAAC,CAAC,EACJ,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9B,YAAY,EAAE,CAAC,GAChB,CAAC;IAeJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,WAAW,CAAC,CAAC,EACT,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9B,YAAY,EAAE,CAAC,GAChB,CAAC;IAeJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,KAAK,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO;IAc/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;IACH,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO;IAc9C,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAWxC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,OAAO,IAAI;QACP,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;KACzD;IAaD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,IAAI,IAAI;QAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAA;KAAC;IAanD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,IAAI;QAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;KAAC;IAexD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,KAAK,CAAC,KAAK,SAAI,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC;IAqCtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4CG;IACH,UAAU,CACN,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAU,EACjB,GAAG,CAAC,EAAE,MAAM,GACb,GAAG,CAAC,CAAC,CAAC;IAwBT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM;IAyB1B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC;IAkCjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,MAAM,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;IAiCjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS;IAW5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAa/B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,IAAI,CACA,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAChB,KAAK,GAAE,MAAU,EACjB,GAAG,CAAC,EAAE,MAAM,GACb,GAAG,CAAC,CAAC,CAAC;IA8CT;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,IAAI,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM;IAoCrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2GG;IACH,MAAM,CACF,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GACtB,GAAG,CAAC,CAAC,CAAC;IAqFT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS;IA2B9B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,OAAO,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM;IAsBxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,QAAQ,CACJ,WAAW,GAAE,MAA0B,GACxC,GAAG,CAAC,CAAC,CAAC;IAeT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmFG;IACH,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAqE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC;IA0B5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,MAAM,IAAI,MAAM;IAehB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC;IAInD,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,sBAAsB;IAmB9B,OAAO,CAAC,aAAa;CAIxB;AAED,oBAAY,YAAY,GAAG,CACvB,KAAK,GACH,KAAK,GACL,MAAM,GACN,MAAM,CACX,CAAA;AACD,oBAAY,GAAG,CAAC,CAAC,SAAS,YAAY,IAAI,CAAC,SAAS,KAAK,GACrD,MAAM,GAAG,KAAK,CAAA;AAClB,oBAAY,GAAG,CAAC,CAAC,SAAS,YAAY,IAAI,CAAC,SAAS,KAAK,GACrD,MAAM,GAAG,KAAK,CAAA;AAClB,oBAAY,IAAI,CAAC,CAAC,SAAS,YAAY,IAAI,CAAC,SAAS,MAAM,GACvD,OAAO,GAAG,KAAK,CAAA;AACnB,oBAAY,IAAI,CAAC,CAAC,SAAS,YAAY,IAAI,CAAC,SAAS,MAAM,GACvD,MAAM,GAAG,KAAK,CAAA;AAClB,oBAAY,SAAS,CAAC,CAAC,SAAS,YAAY,IAAI,CAC5C,GAAG,CAAC,CAAC,CAAC,GACJ,GAAG,CAAC,CAAC,CAAC,GACN,IAAI,CAAC,CAAC,CAAC,GACP,IAAI,CAAC,CAAC,CAAC,CACZ,CAAA;AAED,oBAAY,SAAS,GAAG,QAAQ,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;CAAC,CAAC,CAAA;AAC/D,oBAAY,MAAM,CAAC,CAAC,SAAS,SAAS,IAAI;KAAE,GAAG,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;CAAC,CAAA;AAE/E,aAAK,2BAA2B,GAAG,CAC/B,YAAY,GACV,MAAM,GACN,SAAS,GACT,KAAK,GACL,MAAM,CACX,CAAA;AAED,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAC5C,UAAU,EAAE,2BAA2B,CAC1C;IACG,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC/B;AAED,oBAAY,SAAS,CAAC,CAAC,SAAS,SAAS,IAAI,CACzC,MAAM,CAAC,CAAC,CAAC,GACP;IACE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACb,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;CACpB,CACJ,CAAA;AAED,oBAAY,iBAAiB,CAAC,CAAC,SAAS,SAAS,IAAI,CACjD,SAAS,CAAC,CAAC,CAAC,GACV;IACE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,iBAAiB,CAAC,CAAC,CAAC,CAAA;CACjD,CACJ,CAAA;AAED,oBAAY,iBAAiB,CAAC,CAAC,SAAS,SAAS,IAAI;IACjD,KAAK,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAA;CAC3D,CAAA;AAED,aAAK,kBAAkB,CAAC,CAAC,SAAS,SAAS,IAAI,CAC3C,iBAAiB,CAAC,CAAC,CAAC,GAClB;IACE,aAAa,EAAE,MAAM,CAAA;IACrB,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;CACf,CACJ,CAAA;AAED,oBAAY,mBAAmB,CAAC,CAAC,SAAS,SAAS,IAAI,CACnD,CACI,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EACzB,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KACxB,MAAM,CACd,CAAA;AAED,oBAAY,eAAe,CAAC,CAAC,SAAS,SAAS,IAAI,CAC/C,CAAC,MAAM,IAAI,CAAC,GACV,CAAC,CACC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KACZ,IAAI,CAAC,CACb,CAAA;AACD,oBAAY,WAAW,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,IAAI,CAC9C,CAAC,MAAM,CAAC,CAAC,GACP,CAAC,CACC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KACZ,CAAC,CAAC,CACV,CAAA;AACD,oBAAY,YAAY,CAAC,CAAC,SAAS,SAAS,IAAI,CAC5C,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC,GACf,CAAC,CACC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KACZ,MAAM,CAAC,CAAC,CAAC,CAAC,CAClB,CAAA;AACD,oBAAY,kBAAkB,CAAC,CAAC,SAAS,SAAS,IAAI,CAClD,CAAC,MAAM,OAAO,CAAC,GACb,CAAC,CACH,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KACR,OAAO,CAAC,CAChB,CAAA;AACD,oBAAY,cAAc,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,IAAI,CACjD,CAAC,MAAM,CAAC,CAAC,GACP,CAAC,CACC,aAAa,EAAE,CAAC,EAChB,YAAY,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KACZ,CAAC,CAAC,CACV,CAAA"} -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------