0.1.21 • Published 5 months ago

@zacharygriffee/struct v0.1.21

Weekly downloads
-
License
MIT
Repository
-
Last release
5 months ago

z-struct

A struct-like implementation in vanilla Javascript

A struct keeps variables together in memory. Where a class keeps references together.

This library utilizes compact-encoding library to encode types to buffer.

Installation

npm install @zacharygriffee/struct --save

Import

import {Struct, TYPES, registerType} from "@zacharygriffee/struct";

Usage examples

Create Struct

// struct.js

// Use the design factory to access the variables by name
const Vector3 = Struct(
    ({int32}) => [
        int32("x"),
        int32("y"),
        int32("z")
    ]
);

const numbers = [5,4,3];
const vec3 = Vector3(numbers);

const {x, y, z, buffer: vec3Buffer} = vec3;  // destructurable
const [x, y, z] = vec3;                      // is iterable

vec3.subscribe(
    buffer => {
        // do something with updated buffer
    }
);

vec3.x = 5;                 // Changing the variable updates the buffer.
vec3.next({x: 3, y: 2});    // partial changes.
numbers[0] === 3;           // the array the struct encompasses also changes.
numbers[0] = 42;            // changing the array also changes the struct and buffer and 'causes reaction'

Use the struct

const Vector2 = Struct(({float64}) => [float64("x"), float64("y")]);

const vec2 = Vector2([5.5, 0.3]);           // Okay
const vec2 = Vector2({x: 5.5, y: 0.3});     // Okay

// Pull values from struct instance
const [x,y] = vec2;                         // okay
const {x,y} = vec2;                         // okay

// Get buffer of values
const {buffer} = vec2;                      // okay
const buffer = vec2.buffer;                 // obviously

// rxjs
vec2.subscribe(buffer => {
    /* buffer updates when struct values change */
});

vec2.change$.subscribe(struct => {
    /* updates when struct values change */
})/

// Partial applications.
vec2.next({x: 6.2, y: 0.5});               // okay                    
vec2.next({x: 2});                         // okay
vec2.next([6.2]);                          // okay, must keep structure order
vec2.next([6.2, 0.5]);                     // okay, must keep structure order
vec2.copyFrom([6.2, 0.5]);                 // alias for next

vec2.next(buff);                           // okay 
vec2.copyFrom(buff);                       // alias for next

// free it
vec2.dispose();                            // relieve resources and reactivity
vec2.disposed === true;                    // Dispose leaves 'disposed' property for checking
// you must create a new struct after dispose()

API

Struct(designOrDesignFactory)

There are two ways to create a struct.

One | Design

This way creates an 'arrayLike' struct where you access by index number.

import {TYPES, Struct} from "@zacharygriffee/struct";

const {
    int32, float64
} = TYPES;

const AgeWeight = Struct([Int32, Float64]); // Must pass it as an array.

const {0: age, 1: weight} = AgeWeight([25, 123.52]);            // okay
const [age, weight] = AgeWeight([25, 123.52]);                  // okay

Two | Design Factory

Pass a function as first argument of struct and use destructuring to access the type names. The design factory must return an array.

import {Struct} from "@zacharygriffee/struct";

const vec2 = Struct(({float64}) =>
    [
        float64("x"),
        float64("y")
    ]
);

// OR verbosely
Struct((TYPES) => {
        return [
            TYPES.float64("x"),
            TYPES.float64("y")
        ]
    }
);

struct.copyFrom(otherStructOrStructBuffer) | struct.next(otherStructOrStructBuffer)

The following can be copied from

  • Copy from another struct
  • Copy from another buffer created by similar struct
  • Copy from object that has all or partial properties of current struct
  • Copy from an ordered array all or partial the struct design

struct.dispose()

All properties and functions of struct will become undefined except for a 'disposed' property which will be true. All reactions will cease to operate, and the buffer naturally gced unless referenced elsewhere. The target of the struct will be unattached.

IF your app is ephemeral in nature, like a webpage, you don't really need to dispose.

struct.subscribe(buffer => {})

Get the subscription of buffer changes. Google RxJS usage for more information.

The struct is a Subject and fully compatible with RxJS library. So it can be piped, and shared, and whatever operator combination you can dream of.

struct.change$.subscribe(struct => {})

Get the subscription of value changes. struct will be the full struct of each change.

struct.toArray()

Returns the structure as an array with the order of the design. The resulting array does not cause reactions.

struct.toJSON()

Creates an array of the struct property, values and type name, suitable for non-buffer json serialization.

import {Struct} from "@zacharygriffee/struct";

const Vector2 = Struct(({float64}) =>
    [
        float64("x"),
        float64("y")
    ]
);

const vec2 = Vector2([5.5, 0.12345]);
const obj = vec2.toJSON();
// newVec2 and vec2 are conceptually equal but are not connected.
const newVec2 = Vector2.fromJSON(obj);

Struct.fromJSON(structOrBuffer, [raw=false])

structOrBuffer supports an array, stringified array, or buffer.

raw=false set this to true if the buffer has no framing like leading length of json. If the structure json was created by JSON.stringify with Buffer.from then raw should be set to true.

[Symbol.iterator]

Iterate the properties of the struct ordered to the design.

struct.encode && struct.decode && struct.preencode

See compact-encoding for more information.

The struct functions as a compact-encoding except that it uses its own values to encode and decode into and out of the buffer. This happens reactively anyway so typical usage wouldn't need to use these functions.

However, there are tools out there that take an encoder in their configuration where passing the struct would be more efficient and readable.

const Vector1 = Struct(({int32}) => [int32("x")]);
const vector1 = Vector1([0]);

vector1.change$.subscribe(vector1 => {
    // receives changes.
});

const bee = new Bee({valueEncoding: vector1});
vector1 = await bee.get("someKey"); // updates and returns 'vector1'

I may create a mapper that provides middleware between the changes and the reactions. But you can easily do that with rxjs operators.

import {map, distinctUntilChanged} from "rxjs";

vector1.change$.pipe(
    map(o => o.age),
    distinctUntilChanged()
).subscribe(o => o === changedAge)

TYPES

See compact-encoding for supporting types with the exceptions of raw, fixed and array modifiers.

Alias types:

object alias to json

number alias to float64

Utilities

Struct.registerType(name, encoder)

  • name: Give the type a name. (e.g. int32 is a type name). The type name must be a valid javascript identifier and not conflict with already defined types.
  • encoder: See compact-encoding on how to create an encoder.

Test it

npm test

Roadmap

  • Reference (read and write) the variables straight from the buffer instead of having a buffer change reactively.
  • Implement the structured clone to pass the reference of buffer instead of copying
  • Use a struct design that references a buffer in un-ordered ways. E.G. If I have x,y,z buffer I could create a design that references it like z,x

Distributed under the MIT license. See LICENSE for more information.

0.1.21

5 months ago

0.1.2

6 months ago

0.1.11

6 months ago

0.1.1

6 months ago

0.1.12

6 months ago

0.1.0

6 months ago