Skip to content

🧰 Javascript array-like containers for multithreading

License

Notifications You must be signed in to change notification settings

moomoolive/struct-vec

Repository files navigation

Ψ¨Ψ³Ω… Ψ§Ω„Ω„Ω‡ Ψ§Ω„Ψ±Ψ­Ω…Ω† Ψ§Ω„Ψ±Ψ­ΩŠΩ…

CI Workflow Dependencies Bundle Size

Struct Vec

🧰 Javascript array-like containers for multithreading

Efficiently communicating between js workers is a pain because you are forced either to pass data by structured cloning or represent your data as raw buffers. Structured cloning isn't ideal for performance because it requires de-serialization/serialization every time you pass a message, and raw buffers aren't ideal for productivity because they are esoteric and hard to work with.

This package attempts to solve this problem by allowing you to define data structures called Vecs. Vecs provide an API is similar to javascript Arrays, but are completely backed by SharedArrayBuffers - thus can be passed between workers at zero-cost, while still being intuitive to work with with.

This package was inspired by Google's FlatBuffers, Rust's std::Vec, and @bnaya/objectbuffer package.

Table of Contents

Examples

Quick Start

npm i struct-vec
import {vec} from "struct-vec"

// define the typing of elements in
// vec. Returns a class
const PositionV = vec({x: "f32", y: "f32", z: "f32"})

// initialize a vec
const positions = new PositionV()

// add some elements
for (let i = 0; i < 200; i++) {
  positions.push({x: 1, y: 2, z: 3})
}

console.log(positions.length) // output: 200

// loop over vec
for (let i = 0; i < positions.length; i++) {
  // get element with ".index" method
  const element = positions.index(i)
  console.log(element.x) // output: 1
  console.log(element.y) // output: 2
  console.log(element.z) // output: 3
}

positions.forEach(pos => {
    // use the ".e" method to get
    // the object representation
    // of your element
    console.log(pos.e) // output: {x: 1, y: 1, z: 1}
})

// get a reference to an index
const firstElement = positions.index(0).ref

// remove elements
const allElements = positions.length
for (let i = 0; i < allElements; i++) {
  positions.pop()
}

console.log(positions.length) // output: 0

Initializing a Vec

import {vec} from "struct-vec"

// define what an element should look like 
// definitions are called "struct defs" 
const PositionV = vec({x: "f32", y: "f32", z: "f32"})

// you can initialize your vecs without any arguments
const noArg = new PositionV()

// Or you can specify how much capacity it initially has
// (check the api reference for more info on capacity)
const withCapacity = new PositionV(15_000)
console.log(withCapacity.capacity) // output: 15_000

// Or you can construct a vec from another vec's memory
const fromMemory = PositionV.fromMemory(withCapacity.memory)
console.log(fromMemory.capacity) // output: 15_000

Indexing

Whenever you wish to operate on an element in a vec (get the value or set it), reference a specific field of the element NOT the entire element.

Getting Values at an Index

If you want the value of an element, refer to one of it's fields (yourElement.x for example), the e field to get the entire element by value, or the ref field to get a reference (The e and ref fields are auto-generated for all struct defs).

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()
positions.push({x: 1, y: 2, z: 3})

// πŸ›‘ "wrongValue" doesn't equal {x: 1, y: 2, z: 3}
const wrongValue = positions.index(0)

// βœ… refer to one the fields
const {x, y, z} = positions.index(0)
console.log(x, y, z) // output: 1 2 3

// βœ… get entire element by value
const correctValue = positions.index(0).e
console.log(correctValue) // output: {x: 1, y: 2, z: 3}

// βœ… get a reference to index
const first = positions.index(0).ref
console.log(first.x, first.y, first.z) // output: 1 2 3

// βœ… array destructuring is allowed as well
const [element] = positions
console.log(element) // output: {x: 1, y: 2, z: 3}

Setting Values at an Index

If you want to set the value of an element, refer to one of it's fields (yourElement.x = 2 for example) or reference the e field to set the entire element (The e field is auto-generated for all struct defs). Both these methods work for references as well.

import {vec} from "struct-vec"

const Cats = vec({
     cuteness: "i32",
     isDangerous: "bool", 
     emoji: "char"
})
const cats = new Cats()

cats.push({
     cuteness: 10_000, 
     isDangerous: false, 
     emoji: "😸"
})

// πŸ›‘ does not work - throws error
cats.index(0) = {
     cuteness: 2_876, 
     isDangerous: true, 
     emoji: "πŸ†"
}

// βœ… refer to one the fields
cats.index(0).cuteness = 2_876
cats.index(0).isDangerous = true
cats.index(0).emoji = "πŸ†"
const {cuteness, emoji, isDangerous} = cats.index(0)
console.log(cuteness, emoji, isDangerous) // output: 2876 true πŸ†

// βœ… set entire element at once
cats.index(0).e = {
     cuteness: 2_876, 
     isDangerous: true, 
     emoji: "πŸ†"
}
console.log(cats.index(0).e) // output: {cuteness: 2_876, isDangerous: true, emoji: "πŸ†"}

// βœ… works for references as well
const first = cats.index(0).ref

first.cuteness = 2_876
first.isDangerous = true
first.emoji = "πŸ†"
console.log(first.cuteness, first.emoji, first.isDangerous) // output: 2876 true πŸ†

first.e = {
     cuteness: 2_876, 
     isDangerous: true, 
     emoji: "πŸ†"
}
console.log(first.e) // output: {cuteness: 2_876, isDangerous: true, emoji: "πŸ†"}

Adding Elements

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

for (let i = 0; i < 100_000; i++) {
  // add elements
  positions.push({x: 1, y: 1, z: 1})
}

console.log(positions.index(0).e) // output: {x: 1, y: 1, z: 1}
console.log(positions.index(2_500).e) // output: {x: 1, y: 1, z: 1}
console.log(positions.length) // output: 100_000

Iterating

Imperatively

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

const positions = new PositionV(5_560).fill({x: 4, y: 3, z: 2})

for (let i = 0; i < positions.length; i++) {
  const element = positions.index(i)
  element.x = 20
  element.y = 5
  element.z = element.x + element.y
}

Iterators

Vecs support the majority of iterators that are found on javascript arrays. Check the API Reference for a full list of available iterators.

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

const positions = new PositionV(5_560).fill({x: 4, y: 3, z: 2})

positions.forEach((element, i, v) => {
  element.x = 20
  element.y = 5
  element.z = element.x + element.y
})

const bigPositions = positions.filter((element) => element.x > 10)

// note: vec es6 iterators are slow!!! but work nonetheless
for (const element of positions) {
  element.x = 20
  element.y = 5
  element.z = element.x + element.y
}

Nested Loops

Due to some limitations, vecs usually can only point to one element at a time. To overcome a detachedCursor or ref can be used.

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV(5).fill({x: 1, y: 1, z: 1})

// create a cursor initially pointing at index 0
const innnerCursor = positions.detachedCursor(0)
for (let i = 0; i < positions.length; i++) {
     const outerEl = positions.index(i)
     for (let x = 0; x < vec.length; x++) {
          const innerEl = extraCursor.index(x)
          
          if (innerEl.x === outerEl.x) {
               console.log("same x")
          } else if (innerEl.y === outerEl.y) {
               console.log("same y")
          } else if (innerEl.z === outerEl.z) {
               console.log("same z")
          }
     }
}

Removing Elements

End of Vec

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

positions.push({x: 1, y: 1, z: 1})
const removed = positions.pop()
console.log(removed) // output: {x: 1, y: 1, z: 1}
console.log(positions.length) // output: 0

Start of Vec

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

positions.push({x: 1, y: 1, z: 1})
positions.push({x: 2, y: 2, z: 2})
const removed = positions.shift()
console.log(removed) // output: {x: 1, y: 1, z: 1}
console.log(positions.length) // output: 1

Middle of Vec

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

positions.push({x: 1, y: 1, z: 1})
positions.push({x: 3, y: 3, z: 3})
positions.push({x: 2, y: 2, z: 2})
const [removed] = positions.splice(1, 1)
console.log(removed) // output: {x: 3, y: 3, z: 3}
console.log(positions.length) // output: 2

Swapping Elements

Due to how vecs work internally, swapping can feel awkward. Luckily, there is a swap method that lets you forget about the details.

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

positions.push({x: 1, y: 1, z: 1})
positions.push({x: 3, y: 3, z: 3})

// πŸ›‘ incorrect swap
const tmp = positions.index(0)
positions.index(0) = positions.index(1) // throws Error
positions.index(1) = tmp // throws Error

// βœ… Correct swap
positions.swap(0, 1)
// βœ… This also works, but looks a little
// awkward
const correctTmp = positions.index(0).e
positions.index(0).e = positions.index(1).e
positions.index(1).e = correctTmp

Casting

Array

*Note: Vecs use 32-bit floats, so there will be a loss of precision for decimal numbers when converting an array to a vec.

import {vec, Vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(15).fill({x: 1, y: 2, z: 3})

// cast to array
const pArray = [...p]
console.log(pArray.length) // output: 15
console.log(pArray[0]) // output: {x: 1, y: 2, z: 3}
console.log(Array.isArray(pArray)) // output: true

// create from array
// note: array elements and vec elements must be
// of same type
const pFromArray = PositionsV.fromArray(pArray)
console.log(pFromArray.length) // output: 15
console.log(pFromArray.index(0).e) // output: {x: 1, y: 2, z: 3}
console.log(Vec.isVec(pFromArray)) // output: true
console.log(pFromArray !== p) // output: true

String

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20).fill({x: 1, y: 1, z: 1})

// cast to string
const vecString = p.toJSON()
// can be casted to string like so as well
const vecString1 = JSON.stringify(p)
console.log(typeof vecString) // output: "string"
console.log(vecString1 === vecString) // output: true
// create vec from string
const jsonVec = PositionV.fromString(vecString)

console.log(jsonVec.length) // output: 20
jsonVec.forEach(pos => {
     console.log(pos.e) // output: {x: 1, y: 1, z: 1}
})

Multithreading

Vecs are backed by SharedArrayBuffers and can therefore be sent between workers at zero-cost (check out the benchmarks), irrespective of how many elements are in the vec.

Multithreading with vecs is as easy as this:

index.mjs

import {vec} from "struct-vec"

const Position = vec({x: "f32", y: "f32", z: "f32"})
const positions = new Position(10_000).fill(
    {x: 1, y: 1, z: 1}
)

const worker = new Worker("worker.mjs", {type: "module"})
// pass by reference, no copying
worker.postMessage(positions.memory)

worker.mjs

import {vec} from "struct-vec" 

const Position = vec({x: "f32", y: "f32", z: "f32"})

self.onmessage = ({data}) => {
   Position.fromMemory(data).forEach(p => {
      p.x += 1
      p.y += 2
      p.z += 3
   })
   self.postMessage("finished")
}

SAFETY-NOTES:

  • do not attempt to use length-changing methods while multithreading
  • because vecs are shared across multiple contexts, you can run into real threading issues like data races. Vecs do not come with any type of protections against threading-related bugs, so you'll have to devise your own (mutexes, scheduling, etc.).

Requirements

This package requires javascript environments that support ES6 and SharedArrayBuffers (eg. Node, Deno, supported browsers, etc.).

In order to allow enable SharedArrayBuffers in supported browsers you probably need to fulfill these security requirements.

Also be aware that the runtime compiler (the vec function) uses the unsafe Function constructor behind the scenes to generate vec classes at runtime. This will render vec useless in javascript environments that disallow the use of unsafe constructors such as Function, eval, etc. If it is the case that your environment disallows unsafe constructors, then consider using the build-time compiler (the vecCompile function) instead.

Typescript

Typescript bindings requires version 3.3.3+.

Struct Definitions

All elements in a vec are structs, which can be thought of as strictly-typed objects, that only carry data (no methods). Once a vec is given a struct definition (struct def), structs within the vec can only be exactly the type specified in the definition. This is why it is highly recommended to use this package with Typescript, as setting struct fields with incorrect types can lead to odd behaviors.

Creating a Struct Definition

Creating a struct def is similar to defining a struct def in statically-typed languages (C, Rust, Go, etc.) or an interface in Typescript. Define a struct def by creating an object and mapping fields (with a valid name) to supported data types. Nesting of struct defs is NOT allowed.

// should have floats at "x", "y", and "z" fields
const positionDef = {x: "f32", y: "f32", z: "f32"}

// should have character type at "emoji" field, and
// integer type at "cuteness" field
const catDef = {emoji: "char", cuteness: "i32"}

// should have boolean type at "isScary" and
// integer type at "power" field
const monsterDef = {isScary: "bool", power: "i32"}

Default Struct Fields

Every struct, regardless of definition has some auto-generated fields. Auto-generated fields are:

e : allows you to get and set an entire element.

  • calling .e on an element returns the element by value not by reference. See ref to get element by reference.
import {vec} from "struct-vec"

const Cats = vec({
     cuteness: "i32",
     isDangerous: "bool", 
     emoji: "char"
})
const cats = new Cats()

cats.push({
     cuteness: 10_000, 
     isDangerous: false, 
     emoji: "😸"
})
// get entire element
console.log(cats.index(0).e) // output: {cuteness: 10_000, isDangerous: false, emoji: "😸"}

// set entire element
cats.index(0).e = {
     cuteness: 2_876, 
     isDangerous: true, 
     emoji: "πŸˆβ€β¬›"
}
console.log(cats.index(0).e) // output: {cuteness: 2_876, isDangerous: true, emoji: "πŸˆβ€β¬›"}

ref: returns a reference to an index in a vec.

  • these references refer to an index in a vec NOT the element at the index. Meaning that if the underlying element is moved, the reference will no longer point to it and potentially dangle.
import {vec} from "struct-vec"

const Enemy = vec({power: "i32", isDead: "bool"})
const orcs = new Enemy()
orcs.push(
     {power: 55, isDead: false},
     {power: 13, isDead: false},
     {power: 72, isDead: false},
)
// get a reference to index 0
const firstElement = orcs.index(0).ref
console.log(orcs.index(2).e) // output: {power: 72, isDead: false},
console.log(firstElement.e) // output: {power: 55, isDead: false}

// if underlying element of a ref moves 
// it does not move with it
orcs.swap(0, 1)
console.log(firstElement.e) // output: {power: 13, isDead: false}

// βœ… references can create other references
const firstElementRef = firstElement.ref

Data types

f32

Single-precision floating point number, takes 4 bytes (32 bits) of memory. Similar to javascript's Number type, but with less precision. Also similar to to C's float type.

To define a f32 field:

import {vec} from "struct-vec"

// "num" should be a float type
const NumVec = vec({num: "f32"})
const v = new NumVec()

v.push({num: 1.1})
console.log(v.index(0).num) // output: 1.100000023841858
v.index(0).num = 2.1
// notice the loss of precision
console.log(v.index(0).num) // output: 2.0999999046325684

access-speed-num

This data type very fast in terms of access speed as it maps exactly to a native javascript type.

type-safety-num

If one sets a f32 field with an incorrect type (String type for example), the field will be set to NaN. There a couple of exceptions to this rule, such as if the incorrect type is null, an Array, a BigInt, a Symbol, or a Boolean which will either throw a runtime error, set the field to 0 or 1, depending on the type and javascript engine.

i32

A 32-bit signed integer, takes 4 bytes (32 bits) of memory. Similar to javascript's Number type, but without the ability to carry decimals. Also similar to C's int type.

To define a i32 field:

import {vec} from "struct-vec"

// "num" should be a integer type
const NumVec = vec({num: "i32"})
const v = new NumVec()

v.push({num: 1})
console.log(v.index(0).num) // output: 1
v.index(0).num = 2
console.log(v.index(0).num) // output: 2
v.index(0).num = 2.2
// notice that i32s cannot hold decimals
console.log(v.index(0).num) // output: 2

access-speed-num

same as f32

type-safety-num

same as f32

bool

A boolean value that can be either true or false, takes 1/8 of a byte of memory (1 bit). Same as javascript's Boolean type.

To define a bool field:

import {vec} from "struct-vec"

// "bool" should be boolean type
const BoolVec = vec({bool: "bool"})
const v = new BoolVec()

v.push({bool: true})
console.log(v.index(0).bool) // output: true
v.index(0).bool = false
console.log(v.index(0).bool) // output: false

access-speed-bool

This data type requires a small conversion when getting and setting it's value.

type-safety-bool

When a bool field is set with an incorrect type (Number type for example), the field will be set to true except if the type is falsy, in which case the field will be set to false.

char

One valid unicode 14.0.0 character, takes 4 bytes (32 bits). Same as javascript's String type, except that it is restricted to exactly one character.

To define a char field:

import {vec} from "struct-vec"

// "char" should be character type
const CharVec = vec({char: "char"})
const v = new CharVec()
v.push({char: "πŸ˜€"})
console.log(v.index(0).char) // output: "πŸ˜€"
v.index(0).char = "a"
console.log(v.index(0).char) // output: "a"

access-speed-char

This data type requires a medium level conversion in order to access. Can be up to 100% slower (2x slower) than the f32 type.

type-safety-char

When a char field is set with an incorrect type an error will be thrown. If the inputted type is a string longer than one character, the field will be set to the first character of input. If the inputted type is an empty string, the field will be set to " " (space character).

Disallowed Field Names

Struct field names follow the same naming convention as javascript variables, excluding unicode characters.

Fields also cannot be named e, _viewingIndex, ref,index, self.

import {vec} from "struct-vec"

// πŸ›‘ Not allowed
const v0 = vec({_viewingIndex: "char"})
const v1 = vec({self: "f32"})
const v5 = vec({e: "f32"})
const v2 = vec({["my-field"]: "bool"})
const v3 = vec({["πŸ‘"]: "char"})
const v4 = vec({0: "f32"})

// βœ… allowed
const v11 = vec({x: "f32", y: "f32", z: "f32"})
const v6 = vec({$field: "bool"})
const v7 = vec({_field: "char"})
const v8 = vec({e0: "f32"})
const v9 = vec({myCoolField: "f32"})
const v10 = vec({my$_crazy0_fieldName123: "bool"})

Compilers

This package provides two ways to define vecs, either through the exported vec (runtime compiler) or vecCompile (build-time compiler) functions. Both compilers emit the exact same vec classes.

The key differences between the compilers is that the build-time compiler returns a string containing your vec class which you can write to disk to use in another application, instead of creating a vec class which can be used right away.

Runtime Compiler

Generally, the runtime compiler is easier to work with and that's why all the documentation examples use it. Essentially, the runtime compiler takes your struct def and returns a vec class that can immediately be used.

In case your were wondering, the runtime compiler is quite fast. Defining a vec like so:

const v = vec({x: "f32", y: "f32", z: "f32", w: "f32"})

takes about 0.013 milliseconds in Node. Unless you are planning on defining tens of thousands of vecs, the runtime compiler won't really slow down your application.

The runtime compiler does however make use of the unsafe Function constructor internally. If you know that your javascript environment doesn't allow the use of unsafe constructors (like Function, eval, etc.), then use the build-time compiler.

Also, if you want a build tool like Webpack, ESBuild, Vite, etc. to apply transforms on your vec classes (such as convert them to ES5 syntax), the build-time compiler might be the right choice for you.

Build-time Compiler

The build-time compiler is almost exactly the same as the runtime one. The difference being, after the compiler takes your struct def, it returns a string version of your vec class, instead of a vec class that can be immediately used.

Here's an example:

build.mjs

import fs from "fs"
import {vecCompile} from "struct-vec"

// the path to the "struct-vec" library.
// For the web or Deno, you would
// put the full url to the library.
// for example: https://deno.land/....
// or https://unpkg.com/...
const LIB_PATH = "struct-vec"

// create a struct def, just like with the
// runtime compiler
const def = {x: "bool", y: "f32", z: "char"}

const MyClass = vecCompile(def, LIB_PATH, {
     // generate a javascript class, not typescript
     lang: "js",
     // create class with "export" statement
     exportSyntax: "named",
     className: "MyClass"
})
console.log(typeof MyClass) // output: string
// write the class to disk to use later
// or in another application
fs.writeFileSync("MyClass.mjs", MyClass, {encoding: "utf-8"})

now take a look at the file the class was written to:

MyClass.mjs

// imported dependencies from LIB_PATH
import {Vec} from "struct-vec"
/**
 * @extends {Vec<{"x":"bool","y":"f32","z":"char"}>}
 */
// class was named correctly
// and it was created as a "named" export
export class MyClass extends Vec {
     // some auto generated stuff
     // that looks like it was written by an ape
    static Cursor = class Cursor {
        _viewingIndex = 0
        constructor(self) { this.self = self }
        get y() { return this.self._memory[this._viewingIndex] }; set y(newValue) { this.self._memory[this._viewingIndex] = newValue };
        get z() { return String.fromCodePoint(this.self._memory[this._viewingIndex + 1] || 32) }; set z(newValue) { this.self._memory[this._viewingIndex + 1] = newValue.codePointAt(0) || 32 };
        get x() { return Boolean(this.self._memory[this._viewingIndex + 2] & 1) }; set x(newValue) { this.self._memory[this._viewingIndex + 2] &= -2;this.self._memory[this._viewingIndex + 2] |= Boolean(newValue)};
        set e({y, z, x}) { this.y = y;this.z = z;this.x = x; }
        get e() { return {y: this.y, z: this.z, x: this.x} }        
    }
    get elementSize() { return 3 }
    // here's the inputted struct def
    get def() { return {"x":"bool","y":"f32","z":"char"} }
    get cursorDef() { return MyClass.Cursor }
}

You can now import MyClass into a javascript or typescript file and it will work just like any other vec. Other build options are found in the API Reference.

Unfortunately the build-time compiler does not come with a command-line tool - so you'll need to figure out how you want to generate and store your vec classes.

Caveats

Indexing does NOT Return Element

Indexing into a vec (calling index) is similar to calling next on an iterator. Calling myVec.index(0) will take you to the first element, allowing you to access it's fields, but does not return the element.

To get an element by value use the e field. To get an entire element by reference use the ref field.

The implication of all this, is that a vec can only point to one element at a time. If you want to look at two or more elements at once, you will have to create additional cursors, which can created with the Vec.detachedCursor method.

An example to illustrate this point is a nested for loop:

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

positions.push({x: 1, y: 1, z: 1})
positions.push({x: 3, y: 3, z: 3})
positions.push({x: 2, y: 2, z: 2})

// πŸ›‘ incorrect example
for (let i = 0; i < positions.length; i++) {
     // move vec to index 0
     const el = positions.index(i)
     // move vec from index 0 through 2
     for (let x = 0; x < vec.length; x++) {
          const innerEl = positions.index(x)
     }
     // ❌ vec was moved to index 2 (vec.length) at the 
     // end of the inner loop and is no longer 
     // pointing to index i
     console.log(el.e) // output: {x: 2, y: 2, z: 2}
}

// βœ… Use a detached cursor
// create a cursor initially pointing
// at index 0
const extraCursor = positions.detachedCursor(0)
for (let i = 0; i < positions.length; i++) {
     const el = positions.index(i)
     // move extra cursor from index 0 through 2
     for (let x = 0; x < vec.length; x++) {
          // detached cursors can be move via the "index"
          // method, just like vecs but don't
          // influence were the vec is pointing
          const innerEl = extraCursor.index(x)
     }
     console.log(el.e) // output: what ever is at index i
}

// βœ… Use a reference, also works
// but less efficent
for (let i = 0; i < positions.length; i++) {
     // refs are just a special
     // type of "detachedCursor" under the hood
     const el = positions.index(i).ref
     // move vec from index 0 through 2
     for (let x = 0; x < vec.length; x++) {
          const innerEl = positions.index(x)
     }
     console.log(el.e) // output: what ever is at index i
}

Indexing Out of Bounds

Indexing out of bounds negatively (i < 0) will return undefined just like an Array. Indexing out of bounds past the length (i > vec.length - 1) may or may not return undefined. Sometimes a vec will keep garbage memory at the end to avoid resizing and this is the expected behavior.

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV(5)

positions.push({x: 1, y: 1, z: 1})
positions.push({x: 2, y: 2, z: 2})
positions.pop()
// current length
console.log(positions.length) // output: 1

// βœ… indexing out of bounds negatively (i < 0) 
// always returns undefined
console.log(positions.index(-1).x) // output: undefined

// ⚠️ indexing out of bounds positively (i > vec.length - 1)
// may or may not return undefined
console.log(positions.index(1).x) // output: 2
console.log(positions.index(2).x) // output: 0
console.log(positions.index(10_000).x) // output: undefined

Do Not Mutate Vec Length or Capacity during Multithreading

Do not use any methods that may potentially change the length (or capacity) of a vec during multi-threading. Doing so will lead to unpredictable bugs.

Length-changing methods include: pop, truncate, splice, shift, push, fill, unshift

Capacity-changing methods include: shrinkTo, reserve

Performance Tips

Adding Many Elements

Generally speaking vecs manage their own memory, so you never have to think about resizing or shrinking a vec. However, vecs also provide the ability to expand their memory (or shrink it) on-demand, which is useful when you know ahead of time that you will be adding many elements at once.

Before pushing many elements at once you can use the reserve method to resize the vec's memory as to avoid resizing multiple times and increase performance.

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

// make sure there is enough memory for an additional
// 10,00 elements
positions.reserve(10_000)

for (let i = 0; i < 10_000; i++) {
  positions.push({x: 1, z: 1, y: 1})
}

Removing Many Elements

Similar to when adding many elements, vecs provide a couple of mass removal methods that are more performant.

If you want to remove the last n elements use the truncate method

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV(10_000).fill({x: 1, y: 1, z: 1})

console.log(positions.length) // output: 10_000

// remove last 5_000 elements at once
positions.truncate(5_000)

console.log(positions.length) // output: 5_000

Avoid ES6 Iterators and Indexing

ES6 array destructuring operator (const [element] = vec), spread operator (...vec), and for...of loops (for (const el of vec)) should be avoided except if you want to cast a vec to an array or something similar.

These operators force vecs to deserialize their internal binary representation of structs to objects - which is costly and can cause some unexpected side-effects due to fact that they return elements by value, NOT by reference.

NOTE: the values, entries, keys methods are also es6 iterators.

Avoid Using the e Field Except for Setting an Element

Using the e field to view the value of an element is costly as it forces the vec to deserialize it's internal binary representation into object format (similar to es6 methods). Getting the value of individual fields is far more performant than using the e field. Fortunately, this bottleneck doesn't seem to exist when setting with e.

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV()

positions.push({x: 1, z: 1, y: 1})

// βœ… performant
const {x, y, z} = positions.index(0)
// βœ… also performant
const xVal = positions.index(0).x
const yVal = positions.index(0).y
const zVal = positions.index(0).z

// ⚠️ less performant
const val = positions.index(0).e

// βœ… setting with "e" field is also performant 
// unlike viewing
positions.index(0).e = {x: 2, y: 5, z: 7}

Benchmarks

All test were done over 100 samples, with 4 warmup runs before recording. The multithreaded benchmarks are the only exception to this.

Test machine was a Windows 11/WSL-Ubuntu 20.04 (x64), with a Intel i7-9750H CPU (12 core), and 16 GB RAM.

Iteration

Imperative loop

Iterate over 10 million elements in an imperative manner, adding 10 to one of the element fields.

The raw buffer fields here are Float32Arrays.

Taken on March 31, 2022

Node 16.13.1 (Ubuntu 20.04)

Candidate Result
Raw Buffer 15.28 ms Β±0.96
Array 49.82 ms Β±2.35
Vec 21.69 ms Β±0.74

Deno 1.20.2 (Ubuntu 20.04)

Candidate Result
Raw Buffer 14.79 ms Β±0.89
Array 32.01 ms Β±1.15
Vec 20.63 ms Β±0.76

Chrome 99 (Windows 11)

Candidate Result
Raw Buffer 18.80 ms Β±2.42
Array 38.19 ms Β±2.64
Vec 21.54 ms Β±1.66

Firefox 98 (Windows 11)

Candidate Result
Raw Buffer 23.97 ms Β±0.93
Array 64.30 ms Β±3.13
Vec 54.68 ms Β±1.54

Edge 99 (Windows 11)

Candidate Result
Raw Buffer 17.19 ms Β±1.87
Array 37.82 ms Β±2.10
Vec 21.36 ms Β±1.28

ForEach loop

Iterate over 10 million elements with ForEach iterator, adding 10 to one of the element fields.

Taken on March 24, 2022

Node 16.13.1 (Ubuntu 20.04)

Candidate Result
Array 116.84 ms Β±3.53
Vec 47.84 ms Β±0.77

Deno 1.20.2 (Ubuntu 20.04)

Candidate Result
Array 103.82 ms Β±2.98
Vec 45.57 ms Β±1.14

Chrome 99 (Windows 11)

Candidate Result
Array 126.19 ms Β±5.72
Vec 48.67 ms Β±4.08

Firefox 98 (Windows 11)

Candidate Result
Array 102.04 ms Β±4.00
Vec 149.01 ms Β±10.09

Edge 99 (Windows 11)

Candidate Result
Array 124.70 ms Β±4.44
Vec 48.71 ms Β±2.59

ES6 Iterator loop

Iterate over 10 million elements with ES6's for...of loop and add 10 to one of the element fields.

Taken on March 24, 2022

Node 16.13.1 (Ubuntu 20.04)

Candidate Result
Raw Buffer (imperative) 30.59 ms Β±1.56
Array 53.12 ms Β±1.96
Vec 196.70 ms Β±6.47

Deno 1.20.2 (Ubuntu 20.04)

Candidate Result
Raw Buffer (imperative) 30.45 ms Β±1.54
Array 34.95 ms Β±1.19
Vec 194.63 ms Β±4.82

Chrome 99 (Windows 11)

Candidate Result
Raw Buffer (imperative) 32.13 ms Β±2.15
Array 34.97 ms Β±1.57
Vec 200.56 ms Β±7.61

Firefox 98 (Windows 11)

Candidate Result
Raw Buffer (imperative) 29.21 ms Β±3.35
Array 106.89 ms Β±4.14
Vec 346.72 ms Β±13.57

Edge 99 (Windows 11)

Candidate Result
Raw Buffer (imperative) 31.20 ms Β±1.66
Array 34.46 ms Β±0.83
Vec 200.35 ms Β±6.35

Parallel Loop

Iterate over 8 million elements in a parallel (4 cores) and perform a significant computation. Average of 10 runs, with 4 warmups runs before recording.

Taken on March 31, 2022

Node 16.13.1 (Ubuntu 20.04)

Candidate Result
Single-thread Array 6,415.10 ms Β±469.00
Multithreaded Array 18,833.40 ms Β±246.66
Single-thread Vec 4,856.90 ms Β±120.40
Multithreaded Vec 1,411.40 ms Β±98.34

Deno 1.20.2 (Ubuntu 20.04)

Candidate Result
Single-thread Array 6,541.40 ms Β±167.11
Multithreaded Array 18,204.20 ms Β±172.01
Single-thread Vec 5,487.70 ms Β±43.90
Multithreaded Vec 1,411.40 ms Β±98.34

Chrome 99 (Windows 11)

Candidate Result
Single-thread Array 5,746.00 ms Β±76.65
Multithreaded Array 17,989.40 ms Β±751.12
Single-thread Vec 5,350.60 ms Β±162.57
Multithreaded Vec 1,580.80 ms Β±39.07

Firefox 98 (Windows 11)

Candidate Result
Single-thread Array 6387.00 ms Β±26.23
Multithreaded Array Crashed with no error code
Single-thread Vec 6293.40 ms Β±179.05
Multithreaded Vec 1847.10 ms Β±74.04

Edge 99 (Windows 11)

Candidate Result
Single-thread Array 6,388.00 ms Β±233.65
Multithreaded Array Crashed with code STATUS_BREAKPOINT
Single-thread Vec 5,338.30 ms Β±127.40
Multithreaded Vec 1,569.20 ms Β±73.29

Pushing Elements

Pushing 10 million elements in a row.

"with reserve" label means that vec/array preallocated enough space for all 10 million elements before attempting to push elements.

Preallocation with arrays looks like this:

const arr = new Array(10_000_000)
// start pushing elements

For vecs:

const vec = new PositionV()
vec.reserve(10_000_000)
// start pushing elements 

Taken on March 31, 2022

Node 16.13.1 (Ubuntu 20.04)

Candidate Result
Vec 138.99 ms Β±15.51
Array 690.30 ms Β±227.53
Vec with reserve 76.92 ms Β±4.69
Array with reserve 2305.48 ms Β±85.52

Deno 1.20.2 (Ubuntu 20.04)

Candidate Result
Vec 143.74 ms Β±12.57
Array 1459.62 ms Β±170.93
Vec with reserve 101.21 ms Β±5.23
Array with reserve 1602.00 ms Β±27.78

Chrome 99 (Windows 11)

Candidate Result
Vec 228.77 ms Β±14.33
Array 1373.45 ms Β±262.41
Vec with reserve 129.86 ms Β±62.47
Array with reserve 1459.22 ms Β±35.14

Firefox 98 (Windows 11)

Candidate Result
Vec 630.28 ms Β±9.57
Array 612.50 ms Β±10.76
Vec with reserve 446.65 ms Β±22.07
Array with reserve 2348.31 ms Β±198.37

Edge 99 (Windows 11)

Candidate Result
Vec 243.50 ms Β±11.83
Array 1370.32 ms Β±259.06
Vec with reserve 132.42 ms Β±10.41
Array with reserve 1457.89 ms Β±58.15

API Reference

vec-struct~Vec

The base class that all generated vec classes inherit from.

This class isn't intended to be manually inherited from, as the vec and vecCompile functions will automatically inherit this class and generate the necessary override methods based on your struct definition. The class is still made available however as it has some useful static methods, such as:

isVec : can be used to check if a particular type is a vec at runtime, similar to the Array.isArray method.

The class is generic over T which extends the StructDef type. In other words, the Vec class is type Vec<T extends StructDef>

Kind: inner class of vec-struct

new Vec([initialCapacity])

Param Type Default Description
[initialCapacity] number 15 the amount of capacity to initialize vec with. Defaults to 15.

Example (Basic Usage)

import {vec} from "struct-vec"

const geoCoordinates = vec({latitude: "f32", longitude: "f32"})

// both are valid ways to initialize
const withCapacity = new geoCoordinates(100)
const without = new geoCoordinates()

vec.elementSize : number

The amount of raw memory an individual struct (element of a vec) requires for this vec type. An individual block of memory corresponds to 4 bytes (32-bits).

For example if elementSize is 2, each struct will take 8 bytes.

Kind: instance property of Vec

vec.def : StructDef

The definition of an individual struct (element) in a vec.

Kind: instance property of Vec

vec.length : number

The number of elements in vec. The value is between 0 and (2^32) - 1 (about 2 billion), always numerically greater than the highest index in the array.

Kind: instance property of Vec

vec.capacity : number

The number of elements a vec can hold before needing to resize. The value is between 0 and (2^32) - 1 (about 2 billion).

Kind: instance property of Vec
Example (Expanding Capacity)

import {vec} from "struct-vec"

const Cats = vec({isCool: "f32", isDangerous: "f32"})
// initialize with a capacity of 15
const cats = new Cats(15)
// currently the "cats" array can hold
// up to 15 elements without resizing
// but does not have any elements yet
console.log(cats.capacity) // output: 15
console.log(cats.length) // output: 0

// fill entire capacity with elements
cats.fill({isCool: 1, isDangerous: 1})
// now the cats array will need to resize
// if we attempt to add more elements
console.log(cats.capacity) // output: 15
console.log(cats.length) // output: 15

const capacity = cats.capacity
cats.push({isCool: 1, isDangerous: 1})
// vec resized capacity to accommodate
// for more elements
console.log(capacity < cats.capacity) // output: true
console.log(cats.length) // output: 16

Example (Shrinking Capacity)

import {vec} from "struct-vec"

const Cats = vec({isCool: "f32", isDangerous: "f32"})
// initialize with a capacity of 15
const cats = new Cats(15)
// currently the "cats" array can hold
// up to 15 elements without resizing
// but does not have any elements yet
console.log(cats.capacity) // output: 15
console.log(cats.length) // output: 0
for (let i = 0; i < 5; i++) {
     cats.push({isCool: 1, isDangerous: 1})
}

// vec can hold 3x more elements than we need
// lets shrink the capacity to be memory efficient
console.log(cats.capacity) // output: 15
console.log(cats.length) // output: 5

// shrink vec memory so that length
// and capacity are the same
cats.shrinkTo(0)
console.log(cats.capacity) // output: 5
console.log(cats.length) // output: 5

vec.memory : ReadonlyInt32Array

The binary representation of a vec.

WARNING: It is never recommended to manually edit the underlying memory, doing so may lead to memory corruption.

Kind: instance property of Vec

vec.index(index) β‡’ VecCursor.<StructDef>

Returns a cursor which allows the viewing of the element at the inputted index.

NOTE: this method does not return the actual element at the index. In order to get the entire element at a given index you must use the ".e" method on the cursor. If you want one of the fields of the element just reference the field (for example ".x")

Kind: instance method of Vec
Returns: VecCursor.<StructDef> - A cursor of the target index

Param Type Description
index number the index you want to view

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})

const pos = new PositionsV()

pos.push({x: 1, y: 1, z: 1})
pos.push({x: 1, y: 2, z: 1})
pos.push({x: 1, y: 3, z: 1})

// get entire element at index 0.
// The "e" property comes with all elements
// automatically
console.log(pos.index(0).e) // output: {x: 1, y: 1, z: 1}
console.log(pos.index(1).e) // output: {x: 1, y: 2, z: 1}
// get the "y" field of the element
// at index 2
console.log(pos.index(2).y) // output: 3

vec.at(index) β‡’ VecCursor.<StructDef>

Returns a cursor which allows the viewing of the element at the inputted index.

This method is identical to the index method except that it accepts negative indices. Negative indices are counted from the back of the vec (vec.length + index)

PERFORMANCE-TIP: this method is far less efficient than the index method.

NOTE: this method does not return the actual element at the index. In order to get the entire element at a given index you must use the ".e" method on the cursor. If you want one of the fields of the element just reference the field (for example ".x")

Kind: instance method of Vec
Returns: VecCursor.<StructDef> - A cursor of the target index

Param Type Description
index number the index you want to view. Supports negative indices.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})

const pos = new PositionsV()

pos.push({x: 1, y: 1, z: 1})
pos.push({x: 1, y: 2, z: 1})
pos.push({x: 1, y: 3, z: 1})

// get entire element at index 0.
// The "e" property comes with all elements
// automatically
console.log(pos.index(-1).e) // output: {x: 1, y: 3, z: 1}
console.log(pos.index(-2).e) // output: {x: 1, y: 2, z: 1}
// get the "y" field of the element
// at index 2
console.log(pos.index(-3).y) // output: 1

vec.forEach(callback) β‡’ void

Executes a provided function once for each vec element.

Kind: instance method of Vec

Param Type Description
callback ForEachCallback.<StructDef> A function to execute for each element taking three arguments: - element The current element being processed in the - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV(15).fill({x: 1, y: 1, z: 1})

pos.forEach((p, i, v) => {
     console.log(p.e) // output: {x: 1, y: 1, z: 1}
})

vec.map(callback) β‡’ Array.<YourCallbackReturnType>

Creates a new array populated with the results of calling a provided function on every element in the calling vec.

Kind: instance method of Vec
Returns: Array.<YourCallbackReturnType> - A new array with each element being the result of the callback function.

Param Type Description
callback MapCallback.<StructDef, YourCallbackReturnValue> Function that is called for every element of vec. Each time callbackFn executes, the returned value is added to new Array. Taking three arguments: - element The current element being processed - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV(15).fill({x: 1, y: 1, z: 1})
const xVals = pos.map(p => p.x)

xVals.forEach((num) => {
     console.log(num) // output: 1
})

vec.mapv(callback) β‡’ Vec.<StructDef>

Creates a new vec populated with the results of calling a provided function on every element in the calling vec.

Essentially mapv is the same as chaining slice and forEach together.

Kind: instance method of Vec
Returns: Vec.<StructDef> - A new vec with each element being the result of the callback function.

Param Type Description
callback MapvCallback.<StructDef> Function that is called for every element of vec. Please note that each element is an exact copy of the vec mapv was called on. Taking three arguments: - element The current element being processed - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV(15).fill({x: 1, y: 1, z: 1})
const yAdd = pos.mapv(p => p.y += 2)

yAdd.forEach((p) => {
     console.log(p.e) // output: {x: 1, y: 3, z: 1}
})
pos.forEach((p) => {
     console.log(p.e) // output: {x: 1, y: 1, z: 1}
})
console.log(pos !== yAdd) // output: true

vec.filter(callback) β‡’ Vec.<StructDef>

Creates a new vec with all elements that pass the test implemented by the provided function.

Kind: instance method of Vec
Returns: Vec.<StructDef> - A new vec with the elements that pass the test. If no elements pass the test, an empty vec will be returned.

Param Type Description
callback TruthyIterCallback.<StructDef> A function to test for each element, taking three arguments: - element The current element being processed - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 0})
}
const bigZs = pos.filter(p => p.z > 5)

console.log(bigZs.length) // output: 5
bigZs.forEach((p) => {
     console.log(p.e) // output: {x: 1, y: 2, z: 10}
})
console.log(pos.length) // output: 10
console.log(pos !== bigZs) // output: true

vec.find(callback) β‡’ VecCursor.<StructDef> | undefined

Returns a vec cursor to the first element in the provided vec that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.

Kind: instance method of Vec

Param Type Description
callback TruthyIterCallback.<StructDef> A function to test for each element, taking three arguments: - element The current element being processed - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 0})
}

const nonExistent = pos.find(p => p.z === 100)
console.log(nonExistent) // output: undefined

const exists = pos.find(p => p.z === 10)
console.log(exists.e) // output: {x: 1, y: 2, z: 10}

vec.findIndex(callback) β‡’ number

Returns the index of the first element in the vec that satisfies the provided testing function. Otherwise, it returns -1, indicating that no element passed the test

Kind: instance method of Vec
Returns: number - The index of the first element in the vec that passes the test. Otherwise, -1

Param Type Description
callback TruthyIterCallback.<StructDef> A function to test for each element, taking three arguments: - element The current element being processed - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 0})
}

const nonExistent = pos.findIndex(p => p.z === 100)
console.log(nonExistent) // output: -1

const exists = pos.findIndex(p => p.z === 10)
console.log(exists) // output: 0

vec.lastIndexOf(callback) β‡’ number

Returns the last index at which a given element can be found in the vec, or -1 if it is not present. The vec is searched backwards, starting at fromIndex.

Kind: instance method of Vec
Returns: number - The index of the last element in the vec that passes the test. Otherwise, -1

Param Type Description
callback TruthyIterCallback.<StructDef> A function to test for each element, taking three arguments: - element The current element being processed - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 0})
}

const nonExistent = pos.lastIndexOf(p => p.z === 100)
console.log(nonExistent) // output: -1

const exists = pos.lastIndexOf(p => p.z === 10)
console.log(exists) // output: 4

vec.reduce(callback, initialValue) β‡’ YourCallbackReturnValue

Executes a user-supplied "reducer" callback function on each element of the vec, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the vec is a single value.

NOTE: this implementation is slightly different than the standard vec "reduce" as an initial value is required

Kind: instance method of Vec
Returns: YourCallbackReturnValue - The value that results from running the "reducer" callback function to completion over the entire vec.

Param Type Description
callback ReduceCallback.<StructDef, YourCallbackReturnValue> A "reducer" function that takes four arguments: - previousValue the value resulting from the previous call to callbackFn. - currentValue The current element being processed - currentIndex (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.
initialValue YourCallbackReturnValue A value to which previousValue is initialized the first time the callback is called.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}
const value = pos.reduce(p => {
     return p.x + p.y + p.z
}, 0)
console.log(value) // output: 65

vec.reduceRight(callback, initialValue) β‡’ YourCallbackReturnValue

Applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value.

NOTE: this implementation is slightly different than the standard array "reduceRight", as an initial value is required

Kind: instance method of Vec
Returns: YourCallbackReturnValue - The value that results from running the "reducer" callback function to completion over the entire vec.

Param Type Description
callback ReduceCallback.<StructDef, YourCallbackReturnValue> A "reducer" function that takes four arguments: - previousValue the value resulting from the previous call to callbackFn. - currentValue The current element being processed - currentIndex (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.
initialValue YourCallbackReturnValue A value to which previousValue is initialized the first time the callback is called.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}
const value = pos.reduceRight(p => {
     return p.x + p.y + p.z
}, 0)
console.log(value) // output: 65

vec.every(callback) β‡’ boolean

Tests whether all elements in the vec pass the test implemented by the provided function. It returns a Boolean value.

Kind: instance method of Vec

Param Type Description
callback TruthyIterCallback.<StructDef> A function to test for each element, taking three arguments: - element The current element being processed in the - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}

const everyZis100 = pos.every(p => p.z === 100)
console.log(everyZis100) // output: false

const everyZis10 = pos.every(p => p.z === 10)
console.log(everyZis10) // output: 10

vec.some(callback) β‡’ boolean

Tests whether at least one element in the vec passes the test implemented by the provided function. It returns true if, in the vec, it finds an element for which the provided function returns true; otherwise it returns false. It does not modify the vec.

Kind: instance method of Vec

Param Type Description
callback TruthyIterCallback.<StructDef> A function to test for each element, taking three arguments: - element The current element being processed - index (optional) The index of the current element being processed in the vec. - vec (optional) The vec which method was called upon.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}
pos.push({x: 1, y: 5, z: 0})

const z100Exists = pos.some(p => p.z === 100)
console.log(z100Exists) // output: false

const y5Exists = pos.some(p => p.y === 5)
console.log(y5Exists) // output: true

vec.entries() β‡’ Iterator.<Array.<number, Struct.<StructDef>>>

Returns a new vec Iterator object that contains the key/value pairs for each index in the vec.

PERFORMANCE-TIP: Vecs are very slow when using the ES6 for...of looping syntax. Imperative iteration and higher-order (forEach, map, etc.) iterators are far more efficient.

Kind: instance method of Vec
Returns: Iterator.<Array.<number, Struct.<StructDef>>> - A new vec iterator object
Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}

for (const [index, element] of pos.entries()) {
     console.log(typeof index) // output: number
     console.log(element) // output: {x: 1, y: 2, z: 10}
}

vec.keys() β‡’ Iterator.<number>

Returns a new Array Iterator object that contains the keys for each index in the array.

PERFORMANCE-TIP: Vecs are very slow when using the ES6 for...of looping syntax. Imperative iteration and higher-order (forEach, map, etc.) iterators are far more efficient.

Kind: instance method of Vec
Returns: Iterator.<number> - A new vec iterator object
Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}

for (const index of pos.keys()) {
     console.log(typeof index) // output: number
}

vec.values() β‡’ Iterator.<Struct.<StructDef>>

Returns a new array iterator object that contains the values for each index in the array.

PERFORMANCE-TIP: Vecs are very slow when using the ES6 for...of looping syntax. Imperative iteration and higher-order (forEach, map, etc.) iterators are far more efficient.

Kind: instance method of Vec
Returns: Iterator.<Struct.<StructDef>> - A new vec iterator object
Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV()
for (let i = 0; i < 5; i++) {
     pos.push({x: 1, y: 2, z: 10})
}

for (const element of pos.values()) {
     console.log(element) // output: {x: 1, y: 2, z: 10}
}

vec.slice([start], [end]) β‡’ Vec.<StructDef>

Returns a deep copy of a portion of an vec into a new vec object selected from start to end (end not included) where start and end represent the index of items in that vec. The original vec will not be modified

Kind: instance method of Vec
Returns: Vec.<StructDef> - A new vec containing the extracted elements.

Param Type Default Description
[start] number 0 Zero-based index at which to start extraction. A negative index can be used, indicating an offset from the end of the sequence. slice(-2) extracts the last two elements in the sequence. If start is undefined, slice starts from the index 0. If start is greater than the index range of the sequence, an empty vec is returned.
[end] number vec.length Zero-based index at which to start extraction. A negative index can be used, indicating an offset from the end of the sequence. slice(-2) extracts the last two elements in the sequence. If start is undefined, slice starts from the index 0. If start is greater than the index range of the sequence, an empty vec is returned.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const pos = PositionsV(15).fill({x: 1, y: 2, z: 10})

const posCopy = pos.slice()
console.log(posCopy.length) // output: 15
posCopy.forEach(p => {
     console.log(p.e)// output: {x: 1, y: 2, z: 10}
})

vec.copyWithin(target, [start], [end]) β‡’ Vec.<StructDef>

Shallow copies part of an vec to another location in the same vec and returns it without modifying its length.

Kind: instance method of Vec
Returns: Vec.<StructDef> - The modified vec.

Param Type Default Description
target number Zero-based index at which to copy the sequence to. If negative, target will be counted from the end. If target is at or greater than vec.length, nothing will be copied. If target is positioned after start, the copied sequence will be trimmed to fit vec.length.
[start] number 0 Zero-based index at which to start copying elements from. If negative, start will be counted from the end. If start is omitted, copyWithin will copy from index 0.
[end] number vec.length Zero-based index at which to end copying elements from. copyWithin copies up to but not including end. If negative, end will be counted from the end. If end is omitted, copyWithin will copy until the last index (default to vec.length).

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        // copy position to 0 elements 2, 3, 4
        p.copyWithin(0, 2, p.length)

        console.log(p.index(0).e) // output: {x: 2, y: 4, z: 10}
        console.log(p.index(1).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(2).e) // output: {x: 122, y: 23, z: 8}
        console.log(p.index(3).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(4).e) // output: {x: 122, y: 23, z: 8}

vec.reserve(additional) β‡’ Vec.<StructDef>

Tries to reserve capacity for at least additional more elements to be inserted in the given vec. After calling reserve, capacity will be greater than or equal to vec.length + additional. Does nothing if capacity is already sufficient.

If runtime will not allocate any more memory, an error is thrown.

PERFORMANCE-TIP: use this method before adding many elements to a vec to avoid resizing multiple times.

Kind: instance method of Vec
Returns: Vec.<StructDef> - The expanded vec.

Param Type Description
additional number The amount of elements to allocate memory for.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})

// initialize with space for 15 elements
const p = new PositionV(15)
console.log(p.capacity) // output: 15

// make space for 100 more elements
p.reserve(100)
console.log(p.capacity) // output: 115

vec.reverse() β‡’ Vec.<StructDef>

Reverses an vec in place. The first vec element becomes the last, and the last vec element becomes the first.

Kind: instance method of Vec
Returns: Vec.<StructDef> - The reversed vec.
Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        p.reverse()

        console.log(p.index(0).e) // output: {x: 122, y: 23, z: 8}
        console.log(p.index(1).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(2).e) // output: {x: 2, y: 4, z: 10}
        console.log(p.index(3).e) // output: {x: 1, y: 3, z: 0}
        console.log(p.index(4).e) // output: {x: 2, y: 3, z: 8}

vec.concat(...vecs) β‡’ Vec.<StructDef>

Merge two or more vec. This method does not change the existing vec, but instead returns a new vec.

Kind: instance method of Vec
Returns: Vec.<StructDef> - A new vec instance.

Param Type Description
...vecs Vec.<StructDef> Vecs to concatenate into a new vec. If all value parameters are omitted, concat returns a deep copy of the existing vec on which it is called.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})

const pos = new PositionsV(3).fill({x: 1, y: 1, z: 1})
const pos1 = new PositionsV(2).fill({x: 2, y: 1, z: 1})
const pos2 = new PositionsV(4).fill({x: 3, y: 1, z: 1})

const pos3 = pos.concat(pos1, pos2)
console.log(pos3.length) // output: 9

console.log(pos3 !== pos2) // output: true
console.log(pos3 !== pos1) // output: true
console.log(pos3 !== pos) // output: true

console.log(pos3.index(0).e) // output: {x: 1, y: 1, z: 1}
console.log(pos3.index(3).e) // output: {x: 2, y: 1, z: 1}
console.log(pos3.index(5).e) // output: {x: 3, y: 1, z: 1}

vec.pop() β‡’ Struct.<StructDef> | undefined

Removes the last element from an vec and returns that element. This method changes the length of the vec.

PERFORMANCE-TIP: use the truncate method if you want to efficiently remove many elements from the back, instead of using a loop with the pop method.

Kind: instance method of Vec
Returns: Struct.<StructDef> | undefined - The removed element from the vec; undefined if the vec is empty
Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        console.log(p.pop()) // output: {x: 122, y: 23, z: 8}
        console.log(p.length) // output: 4

        console.log(p.pop()) // output: {x: 233, y: 31, z: 99}
        console.log(p.length) // output: 3

        // pop rest of elements
        p.pop(); p.pop(); p.pop();
        console.log(p.length) // output: 0

        console.log(p.pop()) // output: undefined
        console.log(p.length) // output: 0

vec.truncate(count) β‡’ number

Removes the last n elements from an vec and returns the new length of the vec. If no elements are present in vec, this is a no-op.

PERFORMANCE-TIP: use the this method if you want to efficiently remove many elements from the back, instead the pop method.

Kind: instance method of Vec
Returns: number - New length of the vec

Param Type Description
count number number of elements to remove

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        const newLen = p.truncate(p.length)
        console.log(newLen) // output: 0
        console.log(p.length) // output: 0

vec.fill(value, [start], [end]) β‡’ Vec.<StructDef>

Changes all elements in an vec to a static value, from a start index (default 0) to an end index (default vec.capacity). It returns the modified vec.

Kind: instance method of Vec
Returns: Vec.<StructDef> - The modified vec.

Param Type Default Description
value Struct.<StructDef> Value to fill the vec with. Note: all elements in the vec will be a copy of this value.
[start] number 0 Start index (inclusive), default 0.
[end] number vec.capacity End index (exclusive), default vec.capacity.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(15).fill({x: 1, y: 1, z: 1})
console.log(p.length) // output: 15

p.forEach(pos => {
     console.log(pos.e) // output: {x: 1, y: 1, z: 1}
})

vec.push(...structs) β‡’ number

Adds one or more elements to the end of an Vec and returns the new length of the Vec.

Kind: instance method of Vec
Returns: number - the new length of the vec

Param Type Description
...structs Struct.<StructDef> the element(s) to add to the end of a vec. Element(s) must conform to the struct def, available through the "def" property.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV()
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10}, {x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        console.log(p.length) // output: 5

        console.log(p.index(0).e) // output: {x: 2, y: 3, z: 8}
        console.log(p.index(4).e) // output: {x: 122, y: 23, z: 8}

vec.splice(start, [deleteCount], ...items) β‡’ Vec.<StructDef>

Changes the contents of an vec by removing or replacing existing elements and/or adding new elements in place. To access part of an vec without modifying it, see slice().

Kind: instance method of Vec
Returns: Vec.<StructDef> - A vec containing the deleted elements.

If only one element is removed, an vec of one element is returned.

If no elements are removed, an empty vec is returned.

Param Type Default Description
start The index at which to start changing the vec. If greater than the largest index, no operation is be performed and an empty vec is returned. If negative, it will begin that many elements from the end of the vec. (In this case, the origin -1, meaning -n is the index of the nth last element, and is therefore equivalent to the index of vec.length - n.) If start is negative infinity, no operation is be performed and an empty vec is returned.
[deleteCount] number vec.length An integer indicating the number of elements in the vec to remove from start. If deleteCount is omitted, or if its value is equal to or larger than vec.length - start (that is, if it is equal to or greater than the number of elements left in the vec, starting at start), then all the elements from start to the end of the vec will be deleted. However, it must not be omitted if there is any item1 parameter. If deleteCount is 0 or negative, no elements are removed. In this case, you should specify at least one new element (see below).
...items Struct.<StructDef> The elements to add to the vec, beginning from start. If you do not specify any elements, splice() will only remove elements from the vec.

Example (Removing Elements)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        p.splice(1, 2)
        console.log(p.length) // output: 3

        console.log(p.index(0).e) // output: {x: 2, y: 3, z: 8}
        console.log(p.index(1).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(2).e) // output: {x: 122, y: 23, z: 8}

Example (Adding Elements)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        p.splice(1, 0, {x: 1, y: 1, z: 1}, {x: 2, y: 2, z: 2})
        console.log(p.length) // output: 7

        console.log(p.index(0).e) // output: {x: 2, y: 3, z: 8}
        console.log(p.index(1).e) // output: {x: 1, y: 1, z: 1}
        console.log(p.index(2).e) // output: {x: 2, y: 2, z: 2}
        console.log(p.index(3).e) // output: {x: 1, y: 3, z: 0}
        console.log(p.index(4).e) // output: {x: 2, y: 4, z: 10}
        console.log(p.index(5).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(6).e) // output: {x: 122, y: 23, z: 8}

Example (Adding and Removing Elements Simultaneously)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        p.splice(1, 2, {x: 1, y: 1, z: 1}, {x: 2, y: 2, z: 2})
        console.log(p.length) // output: 5

        console.log(p.index(0).e) // output: {x: 2, y: 3, z: 8}
        console.log(p.index(1).e) // output: {x: 1, y: 1, z: 1}
        console.log(p.index(2).e) // output: {x: 2, y: 2, z: 2}
        console.log(p.index(3).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(4).e) // output: {x: 122, y: 23, z: 8}

vec.shift() β‡’ Struct.<StructDef>

Removes the first element from an vec and returns that removed element. This method changes the length of the vec

Kind: instance method of Vec
Returns: Struct.<StructDef> - The removed element from the vec; undefined if the vec is empty
Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20)
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        console.log(p.shift()) // output: {x: 2, y: 3, z: 8}
        console.log(p.length) // output: 4

        console.log(p.shift()) // output: {x: 1, y: 3, z: 0}
        console.log(p.length) // output: 3

        // shift rest of elements
        p.shift(); p.shift(); p.shift();
        console.log(p.length) // output: 0

        console.log(p.shift()) // output: undefined
        console.log(p.length) // output: 0

vec.unshift(...structs) β‡’ number

Adds one or more elements to the beginning of an vec and returns the new length of the vec.

Kind: instance method of Vec
Returns: number - The new length of the vec.

Param Type Description
...structs Struct.<StructDef> The element(s) to add to the front of the vec

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV()
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        p.unshift({x: 2, y: 4, z: 10})
        p.unshift({x: 2, y: 3, z: 8}, {x: 1, y: 3, z: 0})

        console.log(p.length) // output: 5

        console.log(p.index(0).e) // output: {x: 2, y: 3, z: 8}
        console.log(p.index(1).e) // output: {x: 1, y: 3, z: 0}
        console.log(p.index(2).e) // output: {x: 2, y: 4, z: 10}
        console.log(p.index(3).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(4).e) // output: {x: 122, y: 23, z: 8}

vec.shrinkTo([minCapacity]) β‡’ Vec.<StructDef>

Shrinks the capacity of the vec with a lower bound.

The capacity will remain at least as large as both the length and the supplied value.

If the current capacity is less than the lower limit, this is a no-op.

Kind: instance method of Vec
Returns: Vec.<StructDef> - The shrunken vec.

Param Type Default Description
[minCapacity] number 15 the maximum amount of elements a vec can hold before needing to resize. If negative, it will default to zero. If omitted, defaults to 15.

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})

// initialize with space for 15 elements
const p = new PositionV(15)
console.log(p.capacity) // output: 15

// shrink so that vec can only carry 10
// additional elements
p.shrinkTo(10)
console.log(p.capacity) // output: 10

vec.sort(compareFn) β‡’ Vec.<StructDef>

Sorts the elements of an array in place and returns the sorted array.

The underlying algorithm used is "bubble sort", with a time-space complexity between O(n^2) and O(n).

Kind: instance method of Vec
Returns: Vec.<StructDef> - The sorted vec. Note that the vec is sorted in place, and no copy is made.

Param Type Description
compareFn SortCompareCallback Specifies a function that defines the sort order. Takes two arguments and returns a number: a The first element for comparison. b The second element for comparison. If return value is bigger than 0, b will be sorted before a. If return value is smaller than 0, a will be sorted before b. Otherwise if return is 0, order of the elements will not change.

Example (Ascending Order)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV()
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        console.log(p.length) // output: 5

        p.sort((a, b) => {
            // if a's "x" field is larger than b's
            // swap the position of a and b
            if (a.x > b.x) {
                return 1
            }
            // otherwise keep the same order
            return 0
        })

        console.log(p.index(0).e) // output: {x: 1, y: 3, z: 0}
        console.log(p.index(1).e) // output: {x: 2, y: 3, z: 8}
        console.log(p.index(2).e) // output: {x: 2, y: 4, z: 10}
        console.log(p.index(3).e) // output: {x: 122, y: 23, z: 8}
        console.log(p.index(4).e) // output: {x: 233, y: 31, z: 99}

Example (Descending Order)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV()
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        console.log(p.length) // output: 5

        p.sort((a, b) => {
            // if a's "x" field is smaller than b's
            // swap the position of a and b
            if (a.x < b.x) {
                return -1
            }
            // otherwise keep the same order
            return 0
        })

        console.log(p.index(0).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(1).e) // output: {x: 122, y: 23, z: 8}
        console.log(p.index(2).e) // output: {x: 2, y: 3, z: 8}
        console.log(p.index(3).e) // output: {x: 2, y: 4, z: 10}
        console.log(p.index(4).e) // output: {x: 1, y: 3, z: 0}

vec.swap(aIndex, bIndex) β‡’ Vec.<StructDef>

Swaps the position of two elements. If inputted index is negative it will be counted from the back of the vec (vec.length + index)

Kind: instance method of Vec
Returns: Vec.<StructDef> - the vec with swapped elements

Param Type Description
aIndex number the index of the first element to swap
bIndex number the index of the second element to swap

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV()
        p.push({x: 2, y: 3, z: 8})
        p.push({x: 1, y: 3, z: 0})
        p.push({x: 2, y: 4, z: 10})
        p.push({x: 233, y: 31, z: 99})
        p.push({x: 122, y: 23, z: 8})

        p.swap(0, 2)
        console.log(p.index(0).e) // output: {x: 2, y: 4, z: 10}
        console.log(p.index(2).e) // output: {x: 2, y: 3, z: 8}

        p.swap(1, 3)
        console.log(p.index(1).e) // output: {x: 233, y: 31, z: 99}
        console.log(p.index(3).e) // output: {x: 1, y: 3, z: 0}

vec.toJSON() β‡’ string

Returns a stringified version of the vec it's called on.

Can be re-parsed into vec via the Vec.fromString static method.

NOTE: if any of the underlying memory is set to NaN (via setting with an incorrect type for example) it will be coerced to 0

Kind: instance method of Vec
Returns: string - a string version of a vec
Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV(20).fill({x: 1, y: 1, z: 1})

console.log(p.length) // output: 20
p.forEach(pos => {
     console.log(pos.e) // output: {x: 1, y: 1, z: 1}
})

const vecString = p.toJSON()
console.log(typeof vecString) // output: "string"
// create vec from string representation
const jsonVec = PositionV.fromString(vecString)

console.log(jsonVec.length) // output: 20
jsonVec.forEach(pos => {
     console.log(pos.e) // output: {x: 1, y: 1, z: 1}
})

vec.detachedCursor(index) β‡’ DetachedVecCursor

Creates an cursor that can be used to inspect/mutate a vec, independent of the vec. It has identical functionality as the Vec.index method, expect that you can use it without the vec.

Kind: instance method of Vec

Param Type Description
index number what index should the cursor initially point at

Example (Basic Usage)

import {vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const p = new PositionV()
p.push(
     {x: 1, y: 1, z: 1},
     {x: 2, y: 2, z: 2},
     {x: 3, y: 3, z: 3},
)

// create a cursor and point it at index
// 0
const cursorA = p.detachedCursor(0)
// create a cursor and point it at index
// 1
const cursorB = p.detachedCursor(1)

console.log(cursorA.e) // {x: 1, y: 1, z: 1}
console.log(cursorB.e) // {x: 2, y: 2, z: 2}
console.log(p.index(2).e) // {x: 3, y: 3, z: 3}

// works like the "index" method of vecs
// but can be used independantly
cursorA.index(2).x = 55
console.log(p.index(2).e) // {x: 55, y: 3, z: 3}
console.log(cursorA.e) // {x: 55, y: 3, z: 3}

Vec.def : Readonly.<StructDef>

The definition of an individual struct (element) in a vec class.

Kind: static property of Vec

Vec.elementSize : Readonly.<number>

The amount of raw memory an individual struct (element of a vec) requires for vecs of this class. An individual block of memory corresponds to 4 bytes (32-bits).

For example if elementSize is 2, each struct will take 8 bytes.

Kind: static property of Vec

Vec.isVec(candidate) β‡’ boolean

Checks if input is a of Vec type.

If using the static method on generated class, it will check if input is of same Vec Type of generated class.

If using the static method on the Vec class exported from this package, then it will check if input is of type Vec (more general).

Kind: static method of Vec

Param Type Description
candidate any the value to test

Example (Basic Usage)

import {vec, Vec} from "struct-vec"
const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const CatsV = vec({cuteness: "f32", isDangerous: "bool"})

const cats = new CatsV()
const positions = new PositionsV()

// base class method checks if
// input is a vec type
console.log(Vec.isVec(cats)) // output: true
console.log(Vec.isVec(positions)) // output: true

// generated class method checks
// if input is the same Vec type
// as generated class
// equivalent to instanceof operator
console.log(CatsV.isVec(cats)) // output: true
console.log(CatsV.isVec(positions)) // output: false

console.log(PositionV.isVec(cats)) // output: false
console.log(PositionV.isVec(positions)) // output: true

Vec.fromMemory(memory) β‡’ Vec.<StructDef>

An alternate constructor for vecs. This constructor creates a vec from another vec's memory.

This constructor is particularly useful when multithreading. One can send the memory (memory property) of a vec on one thread to another, via postMessage and initialize an identical vec on the receiving thread through this constructor.

Vec memory is backed by SharedArrayBuffers, so sending it between workers and the main thread is a zero-copy operation. In other words, vec memory is always sent by reference when using the postMessage method of Workers.

Kind: static method of Vec
Returns: Vec.<StructDef> - A new vec

Param Type Description
memory ReadonlyInt32Array memory of another Vec of the same kind

Example (Multithreading)

// ------------ index.mjs ---------------
import {vec} from "struct-vec"
const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const positions = new PositionV(10_000).fill(
     {x: 1, y: 1, z: 1}
)

const worker = new Worker("worker.mjs", {type: "module"})
// pass by reference, no copying
worker.postMessage(positions.memory)

// ------------ worker.mjs ---------------
import {vec} from "struct-vec"
const PositionV = vec({x: "f32", y: "f32", z: "f32"})

self.onmessage = (message) => {
     PositionV.fromMemory(message.data).forEach((pos) => {
         pos.x += 1
         pos.y += 2
         pos.z += 3
     })
}

Vec.fromArray(structArray) β‡’ Vec.<StructDef>

An alternate constructor for vecs. Creates a vec from inputted array, if all elements of array are compliant with struct def of given vec class.

Kind: static method of Vec
Returns: Vec.<StructDef> - A new vec

Param Type Description
structArray Array.<Struct.<StructDef>> array from which to construct the vec.

Example (Basic Usage)

import {vec, Vec} from "struct-vec"

const PositionV = vec({x: "f32", y: "f32", z: "f32"})
const arr = new Array(15).fill({x: 1, y: 2, z: 3})

const positions = PositionsV.fromArray(arr)
console.log(Vec.isVec(positions)) // output: true

Vec.fromString(vecString) β‡’ Vec.<StructDef>

An alternate constructor for vecs. Creates a new vec instance from an inputted string.

String should be a stringified vec. One can stringify any vec instance by calling the toJSON method.

Kind: static method of Vec
Returns: Vec.<StructDef> - A new vec

Param Type Description
vecString string a stringified vec

Example (Basic Usage)

import {vec, Vec} from "struct-vec"

const geoCoordinates = vec({latitude: "f32", longitude: "f32"})

const geo = new geoCoordinates(15).fill({
            latitude: 20.10,
            longitude: 76.52
        })
const string = JSON.stringify(geo)
const parsed = JSON.parse(string)

const geoCopy = geoCoordinates.fromString(parsed)
console.log(Vec.isVec(geoCopy)) // output: true

vec-struct~validateStructDef(def) β‡’ boolean

A helper function to validate an inputted struct definition. If inputted struct def is valid the function true, otherwise it will return false.

Kind: inner method of vec-struct

Param Type Description
def any the struct definition to be validated

Example (Basic Usage)

import {validateStructDef} from "vec-struct"

console.log(validateStructDef(null)) // output: false
console.log(validateStructDef(true)) // output: false
console.log(validateStructDef("def")) // output: false
console.log(validateStructDef({x: "randomType"})) // output: false
console.log(validateStructDef({x: {y: "f32"}})) // output: false

console.log(validateStructDef({x: "f32"})) // output: true
console.log(validateStructDef({code: "f32"})) // output: true

vec-struct~vec(structDef, [options]) β‡’ VecClass.<StructDef>

A vec compiler that can be used at runtime. Creates class definitions for growable array-like data structure (known as a vector or vec for short) that hold fixed-sized objects (known as structs) from your inputted struct definition.

Vecs are backed by SharedArrayBuffers and therefore can be passed across threads with zero serialization cost.

SAFETY-NOTE: this compiler uses the unsafe Function constructor internally. UsevecCompile if you wish to avoid unsafe code. Do note, that vecCompile can only be used at build time.

NOTE: vecs carry fixed-sized, strongly-typed elements that cannot be change once the class is created, unlike normal arrays.

Kind: inner method of vec-struct
Returns: VecClass.<StructDef> - A class that creates vecs which conform to inputted def

Param Type Default Description
structDef StructDef a type definition for the elements to be carried by an instance of the generated vec class
[options] Object
[options.className] string "AnonymousVec" the value of the generated class's name property. Useful for debugging

Example (Basic Usage)

import {vec} from "struct-vec"

// create Vec definition
const PositionV = vec({x: "f32", y: "f32", z: "f32"})
// now initialize like a normal class
const p = new PositionV()

const geoCoordinates = vec({latitude: "f32", longitude: "f32"})
const geo = new geoCoordinates(15).fill({latitude: 1, longitude: 1})

// invalid struct defs throws error
const errClass = vec(null) // SyntaxError
const errClass2 = vec({x: "unknownType"}) // SyntaxError

vec-struct~vecCompile(structDef, pathToLib, [options]) β‡’ string

A vec compiler that can be used at build time. Creates class definitions for growable array-like data structure (known as a vector or vec for short) that hold fixed-sized objects (known as structs) from your inputted struct definition.

Class definitions created by this compiler are the exact same as the one's created by the runtime compiler.

Vecs are backed by SharedArrayBuffers and therefore can be passed across threads with zero serialization cost.

NOTE: this compiler does not come will any command line tool, so you as the user must decide how to generate and store the vec classes emitted by this compiler.

NOTE: vecs carry fixed-sized, strongly-typed elements that cannot be change once the class is created, unlike normal arrays.

Kind: inner method of vec-struct
Returns: string - a string rendition of vec class

Param Type Default Description
structDef StructDef a type definition for the elements to be carried by an instance of the generated vec class.
pathToLib string where the "struct-vec" library is located use the full url if using compiler in web (without build tool) or deno.
[options] Object
[options.bindings] "js" | "ts" "js" what language should vec class be generated in. Choose either "js" (javascript) or "ts" (typescript). Defaults to "js".
[options.exportSyntax] "none" | "named" | "default" "none" what es6 export syntax should class be generated with. Choose either "none" (no import statement with class), "named" (use the "export" syntax), or "default" (use "export default" syntax). Defaults to "none".
[options.className] string "AnonymousVec" the name of the generated vec class. Defaults to "AnonymousVec".

Example (Basic Usage)

import fs from "fs"
import {vecCompile} from "struct-vec"

// the path to the "struct-vec" library.
// For the web or deno, you would
// put the full url to the library.
const LIB_PATH = "struct-vec"

// create Vec definition
const def = {x: "f32", y: "f32", z: "f32"}
const GeneratedClass = vecCompile(def, LIB_PATH, {
     // create a typescript class
     lang: "ts",
     // export the class with "export default"
     // syntax
     exportSyntax: "default",
     className: "GeneratedClass"
})
console.log(typeof GeneratedClass) // output: string
// write the class to disk to use later
// // or in another application
fs.writeFileSync("GeneratedClass", GeneratedClass, {
     encoding: "utf-8"
})

About

🧰 Javascript array-like containers for multithreading

Resources

License

Stars

Watchers

Forks